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

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 자유로운설탕
2017. 3. 11. 21:30 프로그래밍

  이번 시간은 txt 파일에 저장되어 있는 IP 목록을 읽어 각각 루프를 돌리면서 API 를 호출을 한 후, 반환된 json 형태의 데이터에서 원하는 항목을 얻어서, 엑셀에 결과를 저장하는 예제를 시연하려고 한다. 

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  우선 예제를 시작하기 전에, 노파심에 한가지 유의할 부분에 대해서 얘기하려고 한다. 일반적으로 파이썬 예제를 찾다보면 크롤링이나 API 호출 등의 예제들이 많이 나오는데, 해당 방식의 호출에 대해서는 생각보다 좀더 조심스럽게 생각해야 한다는 것이다. 용도에 맞지않게 너무 과도한 호출을 하게 되면, 대상 사이트에서 악성적인 시도로 판단하여 호출 IP 를 블록 할 수도 있고(다만 국내 IP이며, 동적 IP이기 때문에 블록 시키는 기간은 일시적일 가능성이 높다), 또 실습하는 PC가 회사 내 PC 같은 경우 NAT 을 통해 하나의 대표 IP로 나가는 경우가 대부분이기 때문에, 최악의 경우 회사 전체 사람들의 PC에서 해당 사이트로의 연결이 거부되어 버릴 수도 있다. 물론 공식적으로 제공되는 API 의 경우는, 어느 정도 익명 사용자의 잦은 호출을 가정해서 설계 후 제공이 되긴 하지만, 그래도 목적에 필요한 만큼만 호출 하는게 학습 목적에도 맞기 때문에 유의해서 호출 하시기 바란다(혹 지금 실습하는 사이트에서 연락이 올까봐 조금 걱정된다 --;)

 

 

 

 

[API 간단 설명]

  시작하기 전에 API에 대해 확실히 모르는 분을 위해서 간단하게 개념을 설명하려 한다. API(Application Programming Interface)의 예 중 우리가 가장 컴퓨터 안에서 흔하게 볼수 있는 것은 Windows API 이다(물론 이런 OS API와 구분해서 개념지을 수도 있긴 하지만 크게 보면 비슷한듯 하다). 윈도우즈 내의 여러 어플리케이션은 이러한 기본 API 들을 사용해서 윈도우 화면 에 글자를 표시하거나, 윈도우 창과 메뉴를 표시하거나, 효과음 사운드를 내거나, 애니메이션 등을 보여준다(물론 전문화된 다른 API가 또 있을 수 있다). 어찌 보면 파이썬 언어 자체도, 이러한 API 를 잘 사용하기 위해서 상위 수준에서 만들어진 언어라고 봐도 된다. 해당 부분은 리눅스 시스템도 윈도우즈와 마찬가지고 말이다. 비슷한 측면에서 인터넷이 발달한 이후에 이러한 API 들이 컴퓨터 내부를 벗어나서 네트워크(특히 WEB)를 통해서 지원하는 방식이 늘어나게 되었다. 또한 요즘은 SOA(Service Oriented Architecture - 서비스 지향 아키텍쳐), DevOps, MVC, Ajax 같은 설계들이 주류를 형성하게 되면서(개인적으로 봤을때, 해당 항목들의 공통점은 UI와 분리되어 데이터 전달만으로 소통하는 작은 기능, 즉 API 들로의 작은 쪼갬 같다), API 형태의 서비스들이 대세가 된것 같다. (물론 마이크로서비스 같이 좀더 넓은 특성으로 확장하여 지칭하는 용어는 다를 수도 있다)

https://ko.wikipedia.org/wiki/%EC%84%9C%EB%B9%84%EC%8A%A4_%EC%A7%80%ED%96%A5_%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98

 

  우리가 보통 파이썬 등의 언어에서 호출하는 구글, 네이버, 다음 등의 외부 API 는 기존의 웹 환경에서 머리 꼬리를 떼어넨 버전이라고 보면 된다. 그럼 머리 꼬리를 떼면 무엇이 남을까? 웹에서 화려한 화면을 다 떼고 나면 데이터만 남는다.(실제 웹은 브라우저의 꾸밈 역활과 그걸 즐기는 사람들이 없다면 단조로운 태그와 해더들의 세상일지도 모른다) 그럼 데이터를 주고 받을 때도, 사실 기존 get, post 방식으로 보내도 되긴 하는데, 해당 방식은 좀 더 구조적인 데이터를 보내거나 받을 때 다루기 무척 까다롭기 때문에, 첨에는 xml 같은 데이터 형식으로 많이 보내다가 요즘은 실용적인 json 형식 으로 많이 바뀌게 되었다(xml 이 정장이라면 json 은 캐주얼에 가까운 세미정장 같다). 그래서 지금 외부로 노출된 대부분의 API 들은 대부분 xml 이나 json 으로 데이터를 주고 받는 다고 봐도 무방한 듯 싶다.  

 

 

 

 

[WHOIS 사이트 문제] 

  그럼 오늘의 예제를 진행하게 된 배경에 대해 설명한다. IP 주소의 실제 정보(100%정확하진 않지만)를 찾기위해서는 보통 WhoIS라는 웹사이트에서 조회하게 된다.  구글에서 'whois' 라고 검색하면 KISA(한국 인터넷 진흥원)에서 제공하는 WhoIS 페이지가 나온다.  

http://whois.kisa.or.kr/kor/main.jsp

 

 

  위의 페이지에서 밑에 표시된 샘플 정보인 1) '202'로 시작되는 IP 주소를 클릭 하면, 2) 검색 창에 해당 주소가 들어가게 되고, 이후 3) 'Search' 버튼을 클릭 하면 해당 IP 주소에 대해서 저장되어 있는 정보를 보여주게 된다. 보통 사이트 로그에 수상한 호출을 하는 IP가 들어 왔을때, 어떤 나라, 장소에서 들어왔고, 어떤 서비스로 들어왔는지를 아래에 표시된 여러 정보를 참조해서 판단하게 된다.

 

  그런데 문제는 해당 조회 방식을 사용한다면, 조회에 필요한 IP 가 수십개, 수백개로 늘어나는 경우 어떻게 해야 될까? 해당 숫자만큼 매번 페이지를 호출해 확인해야 할 테고, 그럼으로서 생기는 스트레스나 소모되는 시간도 무시할 수 없을 만큼 될 것이다. 물론 웹이나, 프로그램 중 멀티 IP 를 조회해 주는 프로그램도 있지만, 대부분의 프로그램은 국외의 WhoIS 서버(나라, 지역마다 해당 역활을 하는 서버들이 있는 듯 하다)를 호출하기 때문에, 영어로 표시된 정보이기도 하고, 국내 정보도 한국내 WhoIS 만큼 정교하게 나타나지 못할 수 있다. 

 

  그래서 첨에는 많은 웹 크롤링 예제에 있는 것처럼, 해당 페이지를 호출해서 결과 내용을 파싱해서 관련 내용을 뽑아 저장하려고 했었었다(해당 예제와 수반되는 미묘한 문제들은 다음 시간에 다룬다^^). 근데 파싱을 위한 코드들을 찾거나, 상단 페이지의 html 을 분석하는 데에 대해 은근 귀찮음이 밀려오는 상황에서, 마침 해당 페이지의 오른쪽 하단에 자리잡은 OpenAPI 기능 제공이 눈에 띄게 되었다. OpenAPI 라는 것은 비교적 모든 일반 사람들에게 사용을 허가하고 있는 범용 API 이다. 앞의 생선 그림 부분에서 얘기했듯이 API 를 쓰게 되면 일반적으로 정형화된 xml 이나 json 으로 데이터를 받을 수 있으므로, 웹페이지 상에서 html 을 파싱하는 것보다 좀 더 향후 페이지의 변경에 대해서 예외성이 없다.

 

  더 나아가 사이트 들은 보통 페이지 수정 작업 시 크롤링 하는 프로그램을 배려하려 하지 않지만(크롤링은 엄격이 따짐, 유명한 검색엔진들 이외에는 해당 사이트에서 좋아하진 않은 행위기 때문에 그럴 의무도 없고 말이다) , API 의 경우 해당 API 를 이용하여 인터넷 저 너머에서 동작하는 다른 고객의 어플리케이션들이 있을수 있기 때문에, 기존에 해당 API 를 제공한 책임을 생각해서라도 하위 호환성을 고려해서 수정하거나, 기능의 변경이 꼭 필요할땐 차라리 기존 API 를 두고 확장된 대체 API 를 만들 가능성이 많다. 그래서 한번 API 를 호출하는 방식으로 기능을 만들게 되면 해당 사이트가 사라지거나 대대적인 개보수 작업이 있기 전에는 유지보수를 위해 변경할 필요성이 거의 없어지는 장점이 있게 된다.

 

 

 

[API 사용자 등록]

  그럼 첫 페이지에서 아래의 API 링크를 클릭한다.

 

  아무리 모두에게 공개된 OpenAPI 라도 최소한의 호출 체크와 무분별한 사용을 제어하기 위해서 인증키를 발급 하는 경우가 많다. 아래를 보면 이메일을 등록하면, 해당되는 인증 키를 보내준다고 그런다. 그럼 자신이 쓰는 이메일과 사용목적을 적당히 넣고(기관이라 절대 그런일은 없겠지만 마음 한구석에 의심이 남는다면 잘 안쓰는 이메일로 넣는다), '발급신청' 버튼을 누른다. 

 

  잠시 후 자신의 이메일로 가서 확인을 하면, 키가 발급되어 오고 아래의 인증 페이지 링크를 클릭해서 해당 키를 인증 받으라고 그런다. 발급된 키는 앞으로 API 를 사용할때 호출하는 인자에 넣어서 권한을 증명할 키이니, 해당 키를 복사해서 어딘가 잘 저장해 두고, 아래의 인증페이지 URL 을 클릭 해 키 등록 페이지로 이동한다.

 

  이후 발급된 키를 넣고 '등록' 버튼을 누르면, 정상적으로 등록이 되었다고 나온다. 그럼 앞으로 특별히 상도에 어긋나는 행위를 하지 않는한, 해당 WhoIS API 를 당당하게 호출해 사용할 수 있는 권한을 획득하게 된 것이다.      

 

 

 

 

[API 예제 호출 해보기]

  그럼 아래의 Open API 사용 설명 페이지에서 API 를 이용해 IP 를 호출해서 주소를 가져오는 예제를 확인한다. 좀 오타가 있어 헷깔리긴 한데(IP를 도메인 주소라고 적었다), "인터넷주소 검색요청 URL" 의 2번째 예시가 우리가 원하는 기능을 수행하는 API 예제이다.

http://whois.kisa.or.kr/kor/whois/openAPI_KeyCre.jsp

http://whois.kisa.or.kr/openapi/whois.jsp?query=도메인이름&key=발급받은KEY값&answer=[xml,json]

 

 

  해당 값을 편집해, 앞서의 샘플 IP 주소를 호출하는 예제를 만들어 본다. IP 주소는 웹에서 샘플로 사용했던 KISA 주소고, 인증키는 여러분이 발급받은 키로 넣고, answer 형식은 json 으로 한다. beautifulsoup 등을 이용할 수 있는 xml 방식이 편한 사람들은 xml 을 하셔도 되지만, 앞으로 보면 알겠지만, json 도 그 만큼 간단하니 따라와서 확인해 보길 바란다.

http://whois.kisa.or.kr/openapi/whois.jsp?query=202.30.50.51&key=발급받은키&answer=json

 

 

  그럼 해당 쿼리를 복사해서('발급받은키' 부분은 꼭 자기 키로 교체해 넣어야 한다.), IE 주소창에 붙여 넣는다. 그럼 아래와 같이 {, :, " 기호로 구조적으로(실제 나중에 보겠지만 트리 모양이다) 구분된 json 결과가 나온다. 그럼 해당 출력 결과의 문법 기호들을 잘 분석해서 원하는 자료가 있는 위치를 찾거나, 아니면 크롬, 파이어폭스 같은데에 잘 정리해 주는 플러그인이 있을 것 같긴 하다. 하지만 이 시점에서 웹 개발이나, 페이지 구성 분석, 보안 테스팅 등에 두루두루 잘 쓰일 수 있는 하나의 무료 툴을 이용하여 json 결과를 분석하는 방법을 소개하려 한다.(앞으로도 진행 하면서 종종 해당 툴을 쓰게 될 듯 하다)

 

 

 

 

[피들러 설치 - 무료지만 유료 뺨치는 툴] 

  소개하고 싶은 툴은 Fiddler 라는 툴인데(이미 사용해 보신분은 설치는 가볍게 건너뛰어 주시고, 설정 부분만 혹시 몰랐던 부분이 있나 확인하면 된다) 아래와 같이 설치를 한다. 구글에서 'fiddler' 라고 검색하여 아래의 링크로 이동한다.

http://www.telerik.com/fiddler 

 

 

  아래의 무료 다운로드 버튼을 누른다. (회사에서도 무료 사용이 가능하다)

 

 

  이후 이메일 주소를 넣고(아까의 API 키 발급의 경우처럼 메일로 인증 받거나 하는건 아니니 잘 안 쓰는 이메일 주소를 넣음 된다), 아래 그림과 같이 위쪽의 정보 메일 수신 옵션을 uncheck 하고, 아래의 라이센스 동의를 check한다. 이후 'Download' 버튼을 눌러 적절한 폴더에 설치 파일을 저장한다. (뭐 저장안하고 바로 실행해도 된다)

 

 

  설치 파일을 실행해 아래의 라이센스를 'I Agree' 하고, 그 후는 그냥 디폴트 옵션으로 쭉 설치 한다(과정이 간단해서 뒤쪽 스크린샷은 뺌)

 

 

  이후 시작메뉴에서 아래의 Fiddler4를 실행 한다. 이후 아래에 있는 'App Container' 설정이라는 창(아마 10에서만 에지 브라우저 땜에 나오는 듯 하다)이 나오는데, '취소' 를 눌러서 Warning 을 끄자.

 

 

 

  이후 처음 실행하면 브라우저에서 전송하고 가져오는 모든 리소스를 다 표시하기 때문에 몇 가지 사용 편의를 위해 설정할 부분이 있다. 일단 상단의 'Rules' 메뉴로 가서 'Hide Image Request'(보통 웹페이지는 내부에 작은 이미지 들이 많기 때문에 이미지가 모두 리스트에 나타나게 되면, 실제 살펴보길 원하는 웹 프로그램 파일(예:asp, php)을 찾아볼때 방해가 된다. 물론 이미지에 대한 분석이 필요한 경우는 다시 옵션을 바꿔주면 된다)와 'Remove All Encodings'(가져온 페이지 내용에 대해 자동으로 인코딩을 풀어서 피들러에서 바로 평문으로 볼수 있게 해준다) 두 개를 체크 한다. 

 

 

 

  그 다음은 SSL(HTTPS) 복호화 설정이다. 원칙적으로 HTTPS 는 브라우저와 서버 간에 키 교환을 통해 암호화된 데이터가 전송되어서 피들러 같이 중간에서 가로채 보는 툴이 내용을 알아 볼 수 없게 되는데, 컴퓨터 안에 피들러에서 사용할수 있는 테스트용 인증서를 설치하여, 브라우저는 피들러와 암복호화를 해서 통신하고, 다시 피들러가 브라우저 대신 서버와 암복호화를 해서 통신해서 중간에서 내용을 살펴 볼수 있게 해주는 트릭 기법이다. 이 설정이 잘 안 되 있으면, 구글 같이 호출 전체가 HTTPS 로 이루어진 사이트나, 여러 사이트의 로그인 페이지, 회원 정보 페이지 등 정보의 보호를 위해서(법적인 사항이기도 하다) HTTPS 로 호출되는 페이지들은 내용을 볼수 없게 된다(피들러만 쓰는 인증서긴 하지만 혹시나 걱정되는 사람들은 마지막에 설치된 인증서 제거 방법을 명시했으니, 사용 용도가 끝난 후 제거해도 무방하다). 사실 현재 예제에서는 SSL 파싱을 안해도 상관없으니, SSL 설정을 건너뛰셔도 상관없지만, SSL 복호화 기능을 빼고 피들러를 쓰면, http 사이트 들만 볼수 있는 반쪽 짜리 피들러가 되어버려서 설명하는 김에 같이 설명하는 중이다.

 

  그럼 위쪽 메뉴에서 'Tools > Telerik Fiddler Options...' 메뉴를 클릭하여 아래의 옵션 창을 띄운다. 'HTTPS 탭' 을 클릭하고, Capture HTTPS CONNTECTs(https 통신을 캡쳐한다), Decrypt HTTPS traffic(HTTPS 트래픽을 복호화 한다) 를 체크한다.

 

  체크하는 순간에 하단의 Alert 창이 뜬다. HTTPS 트래픽을 가로채기 위해서, 피들러가 루트 인증서를 윈도우에 설치하려 한다는 안내이다. 'YES' 를 클릭 한다.

 

  꼭 설치 하겠냐고, 보안 경고 창이 뜨면, '예'를 누른다. 

 

  다시 또 묻는다 (이게 윈도우즈 10만 이렇게 많이 묻는 듯도 싶다)

 

  최종 확인을 한다.

 

 

  이후 google 페이지에 접속해 본다. 이전에 https 복호화 설정 안했을 때는 안 보이는 구글 페이지의 호출 내용들이 다 보이게 된다.

 

 

 

[부록]

  그럼 아까 얘기한 인증서 제거하는 방법이다. 아까 옵션 창의 'HTTPS' 탭에서 오른 쪽에 있는 'Action' 버튼을 누르고, 'Open Windows Certificate Manager' 를 클릭 한다.

 

  인증서 관리 화면이 뜨면, 왼쪽에서 '신뢰할 수 있는 루트 인증 기관 > 인증서' 를 클릭 한 후, 오른 쪽에 있는 인증서 중, 아까 설치한 'DO_NOT_TRUST_FiddlerRoot' 인증서들을 삭제 한다 (선택하고 마우스 오른쪽 버튼 누르면 '삭제' 메뉴가 나온다). 그럼 설치했던 인증서들은 제거되고, 대신 피들러에서 이제부터는 https 트래픽은 해석 못하게 되버린다. https 복호화 옵션도 같이 끄면 될듯 하다.

 

 

 

 

[피들러의 동작 원리]

  그럼 실제 WhoIS 사이트 보기전에 피들러의 동작 원리에 대해서 얘기하려 한다. 그냥 시키는 데로 사용하는 것 보다는 개념을 이해하며 사용해야 기억에도 잘 남고 스스로의 바라보는 관점도 생기게 되니 말이다.

 

  아래 그림과 같이 웹 브라우저는 웹 서버와 통신을 할때 네트워크 카드를 통해서 직접 통신을 주고 받는다. 근데 Fiddler 같은 웹 프록시들이 설정되게 되면, 네트워크 카드로 패킷을 바로 보내지 않고, 보낼 패킷 들을 피들러에게 전달해 주고, 이후 피들러가 그 패킷을 네트워크 카드로 보내게 된다. 그렇게 함으로서 피들러는 중간에서 내용을 볼수도(사실 우리가 보는 피들러 화면이 바로 그 중간에 잡은 내용을 해석한 것이다) 있고,  (보안 테스트에서 사용하는 기능이지만...) 심지어 중간에서 패킷의 내용을 조작 할 수도 있다. IE 의 개발자 도구와 비슷한 부분 같다고 생각하는 사람도 있겠지만, 개발자 도구는 브라우저 내에서 작동하는 도구라 브라우저에 의존적이고, 피들러 같은 종류의 툴은 브라우저를 벗어나 버린 패킷을 가로채 보여주는 거라서 네트워크에 의존적이다. 할 수 있는 일도 조금 범위가 다르다. 다음 웹 크롤링 시간에 의도한 건 아니지만 그러한 차이 점 중 하나를 보여주는, 개발자 도구로 못보는 화면 데이터를 피들러로 보는 시연이 들어갈 예정이다. 

 

 

  피들러가 브라우저의 전송 내용들을 중개한다는 부분을 간접적으로 확인 해 보자. 아까 보았던 피들러 옵션에서 'Connections' 탭을 클릭 하면, 'Fiiddler linstens on port : 8888' (피들러가 8888 포트에서 신호를 기다리고 있다) 라는 옵션이 있다.

 

 

  피들러를 실행한 상태에서, IE나 크롬 등의 '인터넷 옵션' 의 '연결' 탭을 클릭해 보면, 아래의 '프록시 서버' 라는 옵션이 체크되어 있다(피들러를 끄면 언체크 되니 확인해 보길 바란다). 그 옆의 '고급' 버튼을 누르면, 프록시 설정 창이 뜨는 데 '127.0.0.1'(현재 내 로컬 PC의 IP 주소 - localhost 와 같은 개념이다)주소의 8888포트를 프록시로 사용한다는 옵션이 설정 되 있다. 그 127.0.0.1(내 컴퓨터) 주소에서 8888 포트로 브라우저가 뭔가 보내주기를 기다리는 프로그램이 바로 피들러이다. 반대로 웹서버에서 응답을 받을 때도 피들러가 먼저 응답을 받은 후 브라우저에게 전달해 주게된다. 

 

 

   마지막 팁으로 피들러를 사용 하다보면 옵션에서 이미지는 무시했음에도 불구하고 왼쪽 창에 계속 통신들이 많이 잡혀서 정신 없게 되는데(요즘은 ajax 통신이 많아서 페이지를 열어놓음 계속 뭔가가 잡힐 수도 있다), 아래의 Edit > Remove > All Sessions 메뉴를 이용해 기존 내용을 전체 삭제 하거나, 호출된 리스트를 Ctrl+A 로 모두 선택해서, DEL 키를 누르거나, 그냥 아래에 나온데로 Ctrl +X 를 하거나 하는 3가지 방식으로 모두 클리어 할 수 있다.

 

 

 

 

[피들러로 WhoIS 데이터 호출 보기] 

  그럼 조금 멀리 돌아왔지만(하지만 이제까지 한 얘기도 이전 시간들에 얘기한 프로그램의 외적 요소라는 것은 잊지말자), WhoIS API 의 결과를 피들러로 보면 어떤 부분이 유리한지 살펴 보겠다. 피들러를 켠 상태에서, IE 나 크롬 등에서, 아까 그 샘플 URL(발급 받은 키는 꼭 본인 키로 바꿔 넣어야 한다) 를 주소창에서 실행 한다.(혹시 피들러에서 결과가 잘 안 잡히면 브라우저에서 'F5' 키를 눌러 페이지 재로딩을 해본다. 가끔 이유는 잘 모르겠는데 안 잡힐 때가 있다--;). 아래의 호출된 화면이 나오면, 왼쪽 리스트 창에서 WhoIS API 호출한 항목을 클릭해 선택한 후, 오른쪽에서 Inspectors 탭을 클릭 한다. 

http://whois.kisa.or.kr/openapi/whois.jsp?query=202.30.50.51&key=발급받은키&answer=json

 

 

 

  그리고 오른쪽 아래에 있는 Json 탭을 클릭 한다(피들러의 오른쪽 창은 Request, Response의 두개의 창으로 나눠져 있는데 그 중 아래 쪽 Response 창이다). 그럼 아까 IE 에서는 좀 보기 힘들게 표시되던, Json 결과 데이터가 아래와 같이 트리 형식으로 휠씬 직관적으로 보이게 된다('-' 기호를 클릭함 접히기도 한다~). 저런 모양으로 표시되면 파이썬에서 파싱할 Json 데이터를 분석 할 때 좋고, 그 중 어떤 값이 사용이 필요한지도 쉽게 파악된다. 밑의 json 트리를 기반으로 예제에서 사용할 데이터를 나라이름(CountryCode), 주소(addr), 주소범위(range), 서비스ISP(servName), 입력한 IP 주소(query)  총 5개로 정했다. 

 

 

 

 

[파이썬 코드 만들기]

  그럼 요리를 시작할 환경은 다 준비가 된 것 같고, txt 파일에 IP 들을 한줄에 하나씩 여러개 넣어 두면, 파이썬이 해당 파일을 가져다가 IP 각각을 WhoIS API 를 호출해서, 결과인 json 데이터 안에서 앞에서 결정한 5개 항목을 가져온 후, 엑셀에 정리해 저장하려고 한다. 그림으로 간단히 표시하면 아래와 같다.

 

 

  그럼 다른 시간과 마찬가지로 현재 우리가 모르는게 무엇인지 생각해 본다

1) 텍스트 파일을 읽어와서, 각 IP 들을 루프 돌리는 방법을 모른다.

2) WhoIS API 를 파이썬에서 호출 하는 방법을 모른다.(Request 를 어떻게 해야하나?)

3) Json 응답을 받아와서 원하는 값을 추출하는 방법을 모른다.

(엑셀 저장 부분은 7교시 부록에 함수로 이미 만들어 논 것이 있으니, 가져와 적당히 변형해 쓰면 될듯 하다)

 

 

  먼저 텍스트 파일을 읽어와서 루프를 돌리는 부분을 찾아보자. 텍스트 파일을 읽어와서 각 IP를 리스트에 담아서 예전에 익힌 for 문을 이용해 돌리면 될것 같다, 구글에서 'python file to list' 로 검색한다. 맨 처음 나온 스택오버플로우 페이지를 참조해서, 파일 내용을 리스트로 저장 후, 루프를 돌리는 코드를 만들면 아래와 같다.

http://stackoverflow.com/questions/3925614/how-do-you-read-a-file-into-a-list-in-python

1
2
3
4
5
= open('ip.txt''r')
iplist = f.read().splitlines()
 
for ip in iplist:
  requestWhois(ip)
cs

 

 

 

  그럼 웹 페이지를 인자를 포함해 호출할 수 있는 방법을 찾아야 한다. 일단 한 개의 호출만 정상적으로 하는 코드를 만들어 검증해 보자. 이후 그 코드를 함수로 만들어 호출함 될 것 이다. 구글에서 'python request' 라고 찾아서 아래의 페이지나 한글 페이지 들을 살펴보면, requests 라는 모듈이 쓰이는 것 같다.

http://docs.python-requests.org/en/master/

 

  해당 모듈을 설치하기 위해서 위의 링크를 보다보니 pip 로 인스톨 하는 부분이 있다. 

http://docs.python-requests.org/en/master/user/install/#pip-install-requests

 

  아래와 같이 명령어를 입력하면 특별한 문제 없이 설치가 정상으로 된다~

C:\Python\code>pip install requests
Collecting requests
  Using cached requests-2.13.0-py2.py3-none-any.whl
Installing collected packages: requests
Successfully installed requests-2.13.0

 

 

  호출하는 부분은 대충 해결난거 같으니, json 결과를 받는 방법을 찾아보자.(결과를 받으려면 호출을 해야할테니 코드가 함께 있을 거 같아 두 마리 토끼를 잡을 수도 있을 것 같다) 몇 번 시행 착오를 거쳐서, 'python response json' 이라고 검색을 한후 맨위에서 아래의 페이지를 얻게 되었다.

http://stackoverflow.com/questions/16877422/parsing-json-responses

 

  해당 request 코드와 json 코드를 조합하여, 하나의 IP에 대해서 WhoIS API 를 호출하게 한 코드는 아래와 같다(물론 저렇게 인자를 URL 에 같이 넣지 않고, 따로 분리해 넣는 방법도 있으니, 해당 부분은 위의 Requests 문서 설명 부분을 참고하자. 꼭 POST 형식으로 날려야 할 경우는 반드시 인자의 분리가 필요할 듯 하다). c:\python\code 폴더에 python whois_iptest.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
# -*- coding: utf-8 -*-
import json
import requests  
 
# 요청 세션을 하나 만든다.
= requests.session()
 
# API 를 호출한다.
con = s.get('http://whois.kisa.or.kr/openapi/whois.jsp?query=202.30.50.51&key=발급받은키&answer=json')
 
# 호출 받은 API 결과를 json 형태로 받는다.
json_data = json.loads(con.text)
 
# 결과를 저장할 빈 리스트 생성
WhoIsData = []
 
# 피들러에서 파악했던 값들을 하나씩 추출해 리스트에 담는다.
WhoIsData.append(json_data['whois']['query'])
WhoIsData.append(json_data['whois']['countryCode'])
WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['addr'])
WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['range'])
WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['servName'])
 
# 리스트 출력
print(WhoIsData)
cs

 

  아래와 같이 실행 해보면 정상적으로 값을 가져온다(역시 저장 및 실행 방법을 잘 모르면 2교시에 자세히 설명했으니 보고 오자).

c:\Python\code>python whois_iptest.py
['202.30.50.51', 'KR', '서울특별시 송파구 중대로', '202.30.50.0 - 202.30.51.255', 'KRNIC-NET']

 

 

  그럼 필요한 조각 코드들이 다 만들어 졌으니, 적절히 조합하면 아래와 같이 최종 코드가 나온다. 엑셀 저장 부분은 거의 예전 코드 그대로 이고, 전역 변수 하나 사용하는 거만 차이난다. (전역 변수는 큰 프로그램에서 버그를 양산할 수 있다고 많은 경우 사용하지 말라고 하는 얘기도 많은데, 뭐 필요하니 언어에서 지원하겠지 하고 저 같은 경우는 편해서 종종 사용한다. 여기서는 엑셀의 내용을 적을 라인을 지정하느라 썼는데, 만약 엑셀 라이브러리에서 마지막 라인 정보를 지원해 준다면 안 써도 무방할듯 하다. 전역변수 관련 논의는 아래 링크를 참고한다.)

http://www.gpgstudy.com/forum/viewtopic.php?t=2123

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
# -*- coding: utf-8 -*-
import json
import requests
from openpyxl import Workbook
from openpyxl import load_workbook
#from openpyxl.compat import range
 
wb = Workbook()
 
# 엑셀의 액티브 워크시트 선택
ws = wb.active
ws.title = "whois ip info"
column_num = 2
 
 
 
# 엑셀에 컬럼 저장
def saveColName(cName):
    column_char = 'a'
    for name in cName:
        ws[column_char + '1'= name
        column_char = chr(ord(column_char) + 1)
        
 
# 엑셀에 조회결과 저장
def saveContent(cName):  
  column_char = 'a' 
  global column_num 
  for name in cName:
    ws[column_char + str(column_num)] = name
    column_char = chr(ord(column_char) + 1)
 
# 세션 생성
= requests.session()
 
def requestWhois(searchIP):
  # 엑셀 2번째 줄부터 IP정보를 연달아 저장하기 위해서 전역 변수 선언
  global column_num 
  
  # API 호출 하기(발급받은키는 자신의 키로 수정해야 한다)
  con = s.get('http://whois.kisa.or.kr/openapi/whois.jsp?query=' + searchIP + '&key=발급받은키&answer=json')
 
  # 호출된 결과를 json 형태로 저장
  json_data = json.loads(con.text)
  # 결과를 담을 배열 초기화
  WhoIsData = []
 
  # 각 결과 값을 배열에 저장
  WhoIsData.append(json_data['whois']['query'])
  WhoIsData.append(json_data['whois']['countryCode'])
  WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['addr'])
  WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['range'])
  WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['servName'])
 
  # 결과 화면에 뿌리기
  print(WhoIsData)
 
  # 엑셀에 결과 저장
  saveContent(WhoIsData)
  column_num = column_num + 1
  
 
# 엑셀 1째 줄에 데이터 제목 저장  
excelTitle = ['IP''countryCode''addr''IP_range''servName']
saveColName(excelTitle)
 
# 파일 열어 리스트에 담기
= open('ip.txt''r')
iplist = f.read().splitlines()
 
# 리스트에 담기 IP 정보 얻어 오기
for ip in iplist:
  requestWhois(ip)
 
# 엑셀 저장하기
wb.save("ipinfo.xlsx")
  
cs

 

 

 

 

[에러를 만나다]

  근데 위의 코드를 막상 실제에 사용하다 보니, 에러가 생기는 경우가 존재 한다. 두 가지 겨우인데 외국 IP 나 국내 IP 에서 json 데이터 형태를 다르게 주는 경우가 있어서 였다. 해당 경우를 재현해 보기 위해 외국 아이피를 하나 얻기 위해서 cmd 창에서 중국 사이트인 baidu 사이트에 ping 을 치게 되면 '103.235.46.39' 가 나온다.

c:\Python\code>ping www.baidu.com

Ping www.a.shifen.com [103.235.46.39] 32바이트 데이터 사용:

 

  아까 KISA 샘플 아이피 '202.30.50.51' 와 위의 바이두 IP 두 개를 아래와 같이 두 줄로 ip.txt 에 넣어 c:\python\code 폴더에 저장한다.

202.30.50.51

103.235.46.39

 

  그리고 위의 최종 완성된 소스를 긁어서(소스 내의 '발급받은키' 부분은 꼭 자신의 키로 수정 해야 한다) c:\python\code 폴더에 whoisapi_1st.py 로 저장하고 아래와 같이 실행해 본다.

c:\Python\code>python whoisapi_1st.py
['202.30.50.51', 'KR', '서울특별시 송파구 중대로', '202.30.50.0 - 202.30.51.255', 'KRNIC-NET']
Traceback (most recent call last):
  File "whoisapi_1st.py", line 58, in <module>
  File "whoisapi_1st.py", line 41, in requestWhois
KeyError: 'korean'

 

  이상한 에러가 하나 떨어진다. Korean 이란 키가 없단다(json 에서는 인자를 Key 라고 그러나 보다. 하나 배웠다). 분명히 처음 피들러 결과상에는 있었는데 말이다. 앞의 첫 샘플 IP는 print 결과가 잘 뿌려진거 보니 두번째 해외 IP가 문제인가 보다. 프로그램 상에서 디버깅 해도 좋긴 할테지만, 피들러를 사용해 보면 좀더 간단하게 원인을 발견할 수 있다. 

 

 

  피들러로 아래의 문제 IP 를 넣은 쿼리를 날려서 잡아 본다.(키는 본인 키로 바꾸어야 한다.)

http://whois.kisa.or.kr/openapi/whois.jsp?query=103.235.46.39&key=발급받은키 &answer=json

 

   피들러로 잡아보면 아래와 같이 외국 IP 의 경우 아예 나라(CountryCode) 와 IP(query) 밖에 데이터를 안 보내주고 있다(추가로 나중에 발견한 사실이지만, 국내의 특정 IP 도 ['PI']['netinfo']['addr'] 가 아닌, ['ISP']['netinfo']['addr'] 같이 앞에 있는 json 키가 달라진다)  

 

 

  그럼 급한 나머지 피해갈 코드를 생각하다보니, 현재 상황상 국가와 IP는 무조건 나오니 그대로 두고, 주소 등 뒤의 세 가지 값들이 없는 경우는(마침 없을때는 모두 한꺼번에 없으니) 에러를 내지 않고 그냥 넘어가 버리게 함 어떨까 싶다. 그래서 구글에 'python error pass' 라고 검색한다. 첫번째 스택오버플로우 페이지에서 이런 방식은 다른 모든 종류의 에러도 하나로 퉁 쳐서 패스시키기 때문에 나쁘다곤 하는데, 이해는 되지만 시간이 급하니 에러 코드는 구분 안 하고 일단 사용해 본다.

http://stackoverflow.com/questions/21553327/why-is-except-pass-a-bad-programming-practice

1
2
3
4
5
6
  try:
    WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['addr'])
    WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['range'])
    WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['servName'])
  except:
    pass
cs

 

 

  아래와 같이 try, except, pass 를 사용한 땜빵 코드로 수정한 소스이다.

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
# -*- coding: utf-8 -*-
import json
import requests
from openpyxl import Workbook
from openpyxl import load_workbook
#from openpyxl.compat import range
 
wb = Workbook()
 
# 엑셀의 액티브 워크시트 선택
ws = wb.active
ws.title = "whois ip info"
column_num = 2
 
 
 
# 엑셀에 컬럼 저장
def saveColName(cName):
    column_char = 'a'
    for name in cName:
        ws[column_char + '1'= name
        column_char = chr(ord(column_char) + 1)
        
 
# 엑셀에 조회결과 저장
def saveContent(cName):  
  column_char = 'a' 
  global column_num 
  for name in cName:
    ws[column_char + str(column_num)] = name
    column_char = chr(ord(column_char) + 1)
 
# 세션 생성
= requests.session()
 
def requestWhois(searchIP):
  # 엑셀 2번째 줄부터 IP정보를 연달아 저장하기 위해서 전역 변수 선언
  global column_num 
  
  # API 호출 하기(발급받은키는 자신의 키로 수정해야 한다)
  con = s.get('http://whois.kisa.or.kr/openapi/whois.jsp?query=' + searchIP + '&key=발급받은키&answer=json')
 
  # 호출된 결과를 json 형태로 저장
  json_data = json.loads(con.text)
  # 결과를 담을 배열 초기화
  WhoIsData = []
 
  # 각 결과 값을 배열에 저장
  WhoIsData.append(json_data['whois']['query'])
  WhoIsData.append(json_data['whois']['countryCode'])
 
  try:
    WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['addr'])
    WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['range'])
    WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['servName'])
  except:
    pass
    
  try:
    WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['addr'])
    WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['range'])
    WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['servName'])
  except:
    pass
 
  # 결과 화면에 뿌리기
  print(WhoIsData)
 
  # 엑셀에 결과 저장
  saveContent(WhoIsData)
  column_num = column_num + 1
  
 
# 엑셀 1째 줄에 데이터 제목 저장  
excelTitle = ['IP''countryCode''addr''IP_range''servName']
saveColName(excelTitle)
 
# 파일 열어 리스트에 담기
= open('ip.txt''r')
iplist = f.read().splitlines()
 
# 리스트에 담기 IP 정보 얻어 오기
for ip in iplist:
  requestWhois(ip)
 
# 엑셀 저장하기
wb.save("ipinfo.xlsx")
cs

 

  에러 처리가 된 소스를(소스 내의 '발급받은키' 부분은 자신의 키로 수정 해야 된다.) c:\python\code 폴더에 whoisapi_2nd.py 로 저장하고 아래와 같이 실행해 본다. 잘 동작이 된다.

c:\Python\code>python whoisapi_2nd.py
['202.30.50.51', 'KR', '서울특별시 송파구 중대로', '202.30.50.0 - 202.30.51.255', 'KRNIC-NET']
['103.235.46.39', 'HK']

 

 

 

 

[최종 - 땜빵 코드 없애기]

  이렇게 마무리하게 되면, 왠지 불완전한 땜빵 코드를 안내하고 마치는거 같은 무거운 마음이 들어서(사실 위의 request 세션 같은 경우도 마지막에 세션을 닫아야 할거 같은 생각은 들지만 그런건 무시하고), 정도를 걷기위해 json 결과의 키 값이 없을 때 예외처리하는 방법을 구글에서 찾아본다. 여러번의 검색어 시행착오를 거쳐서 'python json key exist' 라고 검색해 아래의 스택오버플로우 페이지를 찾는다. 기대한 바와 다르게 하위에 특정 값이 없음을 바로 체크 할 순 없고, 계단식으로 1단계씩 차례로 체크해야 하는거 같다.  (예를 들면 json_data['whois']['korean']['PI']['netinfo']['addr'] 라는 json 데이터가 있다면  'addr' in json_data['whois'] 처럼 트리 단계를 점프해서 찾을 순 없다. 좀 답답해 보이지만, 디렉토리 처럼 'whois' 가 있는지 체크하고, 그 담에 'korean' 을 체크하고, 그담에 ... 하고 해야하는 식이다.
http://stackoverflow.com/questions/24898797/check-if-key-exists-and-iterate-the-json-array-using-python

 

  해당 에러 처리 코드를 구성하면 아래와 같고,

1
2
3
4
5
6
7
8
9
10
  # json 인자가 있는지 체크하는 식으로 에러처리 바꿈
  if 'korean' in json_data['whois']:
    if 'PI' in json_data['whois']['korean']:
      WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['addr'])
      WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['range'])
      WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['servName'])
    elif 'ISP' in json_data['whois']['korean']:
      WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['addr'])
      WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['range'])
      WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['servName'])
cs

 

 

  최종 코드는 아래와 같다. (뭐 혹시 아래도 뭔가 맘에 안드시면 제 능력 밖이라고 밖에는 --;)

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
# -*- coding: utf-8 -*-
import json
import requests
from openpyxl import Workbook
from openpyxl import load_workbook
#from openpyxl.compat import range
 
wb = Workbook()
 
# 엑셀의 액티브 워크시트 선택
ws = wb.active
ws.title = "whois ip info"
column_num = 2
 
 
 
# 엑셀에 컬럼 저장
def saveColName(cName):
    column_char = 'a'
    for name in cName:
        ws[column_char + '1'= name
        column_char = chr(ord(column_char) + 1)
        
 
# 엑셀에 조회결과 저장
def saveContent(cName):  
  column_char = 'a' 
  global column_num 
  for name in cName:
    ws[column_char + str(column_num)] = name
    column_char = chr(ord(column_char) + 1)
 
# 세션 생성
= requests.session()
 
def requestWhois(searchIP):
  # 엑셀 2번째 줄부터 IP정보를 연달아 저장하기 위해서 전역 변수 선언
  global column_num 
  
  # API 호출 하기(발급받은키는 자신의 키로 수정해야 한다) 
  con = s.get('http://whois.kisa.or.kr/openapi/whois.jsp?query=' + searchIP + '&key=발급받은키&answer=json')
 
  # 호출된 결과를 json 형태로 저장
  json_data = json.loads(con.text)
  # 결과를 담을 배열 초기화
  WhoIsData = []
 
  # 각 결과 값을 배열에 저장
  WhoIsData.append(json_data['whois']['query'])
  WhoIsData.append(json_data['whois']['countryCode'])
 
  # json 인자가 있는지 체크하는 식으로 에러처리 바꿈
  if 'korean' in json_data['whois']:
    if 'PI' in json_data['whois']['korean']:
      WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['addr'])
      WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['range'])
      WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['servName'])
    elif 'ISP' in json_data['whois']['korean']:
      WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['addr'])
      WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['range'])
      WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['servName'])
  
 
  # 결과 화면에 뿌리기
  print(WhoIsData)
 
  # 엑셀에 결과 저장
  saveContent(WhoIsData)
  column_num = column_num + 1
  
 
# 엑셀 1째 줄에 데이터 제목 저장  
excelTitle = ['IP''countryCode''addr''IP_range''servName']
saveColName(excelTitle)
 
# 파일 열어 리스트에 담기
= open('ip.txt''r')
iplist = f.read().splitlines()
 
# 리스트에 담기 IP 정보 얻어 오기
for ip in iplist:
  requestWhois(ip)
 
# 엑셀 저장하기
wb.save("ipinfo.xlsx")
cs

 

 

  최종 소스를(소스 내의 '발급받은키' 부분은 자신의 키로 수정 해야 한다) c:\python\code 폴더에 whoisapi_3rd.py 로 저장하고 아래와 같이 실행해 본다. 잘 동작이 된다. c:\Python\code>python whoisapi_3rd.py
['202.30.50.51', 'KR', '서울특별시 송파구 중대로', '202.30.50.0 - 202.30.51.255', 'KRNIC-NET']
['103.235.46.39', 'HK']

 

  해당 폴더에 생성된 ipinfo.xlsx 파일도 확인해보면 잘 저장되어 있다.

 

 

  이렇게 피들러땜에 좀 설명이 길었던(하지만 꼭 한번 소개 하고 싶었던) 10교시를 마치고, 다음 시간에는 웹 크롤링에 대해서 몇가지 생각해 볼 주제를 Beautiful Soap 과 정규 표현식, 이번 시간 예제와 연결해서 진행해볼 예정이다. 크롤링 부터는 슬슬 자동화의 영역에 발을 들이는 기분이다. 피들러는 구글을 찾아보면 자료가 많이 있으니(다만 사이트마다 올리는 사람의 관심 분야에 따라 설명 포커스는 조금 틀린거 같다) 참고해서, 꼭 이리저리 사용해 보길 바란다. 피들러 홈페이지의 사용 설명을 읽음 더욱 좋다. 개인적으로는 웹세계의 '스위스 아미 나이프' 라고 평가한다.^^ 계속 잘 진행 된다면 언젠가 여기에 피들러 관련한 이런저런 포스팅도 하고 싶긴 하다.

 

 

[보충 #2]

  문의가 와서 보니 openpyxl 최신버전에서는 아래 import 문이 에러가 나네요. 지금 보니 사실상 코드안에서 사용하지 않는 부분이라서 아예 빼버림 에러가 발생안합니다. 위의 예제들에서는 주석처리 해놨어요.

from openpyxl.compat import range

 

 

2017.3.12 by 자유로운설탕 
cs

 

posted by 자유로운설탕
prev 1 next