웹페이지의 국가별 액세스 수 시각화

주식이 아닌 LGTM (∀ ·)

목적



분명한 해외로부터의 공격적 접근을 이해하기 쉽게

개요



사전 준비


  • apache or nginx 로깅을 매일 되도록 설정하십시오.
    (방법은 몇 가지 있으므로 직접 확인해보십시오)
  • GeoLite2-City.mmdb
    를 사용합니다.
    GeoIP을 사용하므로 등록하십시오.
    라이선스와 취득 방법이 작년 말에 변경이 있었으므로, 여기 등을 참고해 보세요

  • 환경



    서버: 클라우드(AWS lightsail)
    OS: CentOS 7.8
    웹 서버: apache 2.4.6
    Python: 3.6.4
    apacheRoot:/var/www/html/
    apache 로그:/var/log/httpd/
    [root@fishkiller ~]# cat /etc/centos-release
    CentOS Linux release 7.8.2003 (Core)
    [root@fishkiller ~]# httpd -v
    Server version: Apache/2.4.6 (CentOS)
    Server built:   Apr  2 2020 13:13:23
    [root@fishkiller ~]# python --version
    Python 3.6.4
    [root@fishkiller ~]# cat /etc/httpd/conf/httpd.conf|grep Root|grep -v "\#"
    ServerRoot "/etc/httpd"
    DocumentRoot "/var/www/html"
    
    [root@fishkiller httpd]# pwd
    /var/log/httpd
    

    환경은 정직 python이 3 계이면 무엇이든 좋다.
    [root@fishkiller ~]# pwd
    /root
    
    [root@fishkiller ~]# ls
    app bin
    
    [root@fishkiller ~]# ls app/
    accessip
    
    

    원하는 곳에 원하는대로 작업 디렉토리를 만드십시오.

    디렉토리
    ~/ap
    |--accessip
        |--main.py
        |--GeoLite2-City.mmdb
    

    로그에서 액세스한 IP 추출


    cat /var/log/httpd/access_`date +%Y%m`$((`date +%d`-1)).log|awk '{print $1}'|sort > /root/app/httpd-ip.txt
    

    미안. py


    #!/usr/bin/python
    import numpy as np
    import matplotlib as mpl
    mpl.use('Agg')
    import matplotlib.pyplot as plt
    import re
    import requests
    import geoip2.database
    import folium
    import japanize_matplotlib 
    
    
    
    # Line-notifier設定
    
    token = 'トークン'
    
    api = 'https://notify-api.line.me/api/notify'
    
    #Geoipのデータベースを読み込む
    reader = geoip2.database.Reader('/root/bin/log/GeoLite2-City.mmdb')
    
    
    # 読み込むファイルを開く
    f = open('/root/bin/log/httpd-ip.txt')
    
    lines = f.readlines()
    f.close()
    
    
    country = []
    city = []
    
    
    
    def main():
    
    
        map = folium.Map(location=[35,135],zoom_start=4)#map
    
        for line in lines:
    
            ip = line.rstrip('\n')
    
            print("IP",ip) #IP
    
    
            data = reader.city(ip)
            country.append(data.country.name)
            print ("国:", data.country.name) #国
            #print ("Subdivisions: ", data.subdivisions.most_specific.name)#州・県
            city.append(data.city.name)
            print ("都市: ", data.city.name) # 市町村
            # print ("経度,緯度: ",data.location.longitude,data.location.latitude)
            #print ("経度: ",data.location.longitude)
            #print ("緯度: ",data.location.latitude)
    
            folium.Marker([data.location.latitude,data.location.longitude],popup=data.city.name).add_to(map) # ([経度,緯度],popup="表示名")
    
    
            #print ("軽度: ", data.location.latitude) 
            #print ("緯度: ", data.location.longitude) 
            #print ("タイムゾーン:", data.location.time_zone) 
    
    
        map.save('/root/app/accessip/http_map.html')
    
    
        count = len(country)
        CN = country.count('China') #中国
        CA = country.count('Canada') #カナダ
        US = country.count('United States') #アメリカ
        VN = country.count('Vietnam') #ベトナム
        PH = country.count('Philippines') #フィリピン
        RU = country.count('Russia') #ロシア
        UA = country.count('Ukraine') #ウクライナ
        IT = country.count('Italy') #イタリア
        ES = country.count('Spain') #スペイン
        TW = country.count('Taiwan') #台湾
        KR = country.count('Republic of Korea') #韓国
        IN = country.count('India') #インド
        JP = country.count('Japan') #日本
        others = count-CN-CA-US-VN-PH-RU-UA-IT-ES-TW-KR-IN-JP
    
    
    
        circle(CN,CA,US,VN,PH,RU,UA,IT,ES,TW,KR,IN,JP,others)
    
        mess(CN,CA,US,VN,PH,RU,UA,IT,ES,TW,KR,IN,JP,others,count)
    
        check(CN,CA,US,VN,PH,RU,UA,IT,ES,TW,KR,IN,JP,others,count)
    
        cplot(CN,CA,US,VN,PH,RU,UA,IT,ES,TW,KR,IN,JP)
    
    
    def circle (CN,CA,US,VN,PH,RU,UA,IT,ES,TW,KR,IN,JP,others):
    
        graph = np.array([CN,CA,US,VN,PH,RU,UA,IT,ES,TW,KR,IN,JP,others])
        #x = ["CN","CA","US","VN","PH","RU","UA","IT","ES","TW","KR","IN","others"]
        x = ["中国","カナダ","アメリカ","ベトナム","フィリピン","ロシア","イギリス","イタリア","スペイン","台湾","韓国","インド","日本","その他"]
    
        plt.style.use('ggplot')
        plt.rcParams.update({'font.size':15})
    
        plt.pie(graph,labels=x,autopct=lambda p:'{:.1f}%'.format(p) if p>=5 else '')
    
        plt.savefig('/root/app/accessip/apache-access.png')
        # plt.show()
    
    def cplot(CN,CA,US,VN,PH,RU,UA,IT,ES,TW,KR,IN,JP):
    
        map1 = folium.Map(location=[35, 135], zoom_start=3)
    
        states = (
        {'lat': 39.5427, 'lon': 116.2350, 'value': CN, 'name': '中国'},
        {'lat': 45.4215, 'lon': -75.6971, 'value': CA, 'name': 'カナダ'},
        {'lat': 38.907192, 'lon': -77.036871, 'value': US, 'name': 'アメリカ'},
        {'lat': 21.0279, 'lon':105.851, 'value': VN, 'name': 'ベトナム'},
        {'lat': 14.609, 'lon': 121.0222, 'value': PH, 'name': 'フィリピン'},
        {'lat': 55.7558, 'lon': 37.6173, 'value': RU, 'name': 'ロシア'},
        {'lat': 50.4501, 'lon': 30.5234, 'value': UA, 'name': 'ウクライナ'},
        {'lat': 41.9027, 'lon': 12.4963, 'value': IT, 'name': 'イタリア'},
        {'lat': 40.4167, 'lon': -3.7037, 'value':  ES, 'name': 'スペイン'},
        {'lat': 25.251, 'lon': 121.3154, 'value': TW, 'name': '台湾'},
        {'lat': 37.34, 'lon': 126.59, 'value': KR, 'name': '韓国'},
        {'lat': 20.5936, 'lon': 78.9628, 'value': IN, 'name': 'インド'},
        {'lat': 35.4122, 'lon': 139.4130, 'value': JP, 'name': '日本'}
    
        )
    
        # 円の大きさをわかりやすくするための重み
        WEIGHT = 0.6
    
        # 都市ごとにマーカーを追加(数が増えると辛いため、一括追加が今後の課題)
        for state in states:
            folium.CircleMarker(
                [state['lat'], state['lon']],
                radius=state['value'] * WEIGHT,
                popup=state['name'],
                color='#3186cc',
                fill_color='#3186cc',
            ).add_to(map1)
    
        map1.save('/root/app/accessip/http_map1.html')
    
    
    
    def mess (CN,CA,US,VN,PH,RU,UA,IT,ES,TW,KR,IN,JP,others,count):
    
        ## Lineに送る
        # meesage = 送るメッセージ
        message = '\n' + '中国:' + str(CN/count*100) + '%\n' + 'カナダ:' + str(CA/count*100) + '%\n' + 'アメリカ:' + str(US/count*100) + '%\n' + 'ベトナム:' + str(VN/count*100) + '%\n' + 'フィリピン:' + str(PH/count*100) + '%\n' + 'ロシア:' + str(RU/count*100) + '%\n' + 'ウクライナ:' + str(UA/count*100) + '%\n' + 'イタリア:' + str(IT/count*100) + '%\n' + 'スペイン:' + str(ES/count*100) + '%\n' + '台湾:' + str(TW/count*100) + '%\n' + '韓国:' + str(KR/count*100) + '%\n' + 'インド:' + str(IN/count*100) + '%\n' + '日本' + str(JP/count*100) + '%\n'  +'その他:' + str(others/count*100) + '%\n'+'MAP: '+'http://fishkiller.info/banmap/index.html'+'\n'+'国MAP: '+'http://18.177.113.234/httpd/map1.html'
    
    
        payload = {'message': message}
        files = {"imageFile": open("apache-access.png", "rb")}
    
        #headers = {'Authorization': 'Bearer ' + line_notify_token}  
        #line_notify = requests.post(line_notify_api, data=payload, headers=headers)
    
        headers = {'Authorization': 'Bearer ' + token}
        line_notify = requests.post(api, data=payload, headers=headers, files=files)
    
    
    def check (CN,CA,US,VN,PH,RU,UA,IT,ES,TW,KR,IN,JP,others,count):
        print("中国:",CN)
        print(CN/count*100,"%")
    
        print("カナダ:",CA)
        print(CA/count*100,"%")
    
        print("アメリカ:",US)
        print(US/count*100,"%")
    
        print("ベトナム:",VN)
        print(VN/count*100,"%")
    
        print("フィリピン:",PH)
        print(PH/count*100,"%")
    
        print("ロシア:",RU)
        print(RU/count*100,"%")
    
        print("ウクライナ:",UA)
        print(UA/count*100,"%")
    
        print("イタリア:",IT)
        print(IT/count*100,"%")
    
        print("スペイン:",ES)
        print(ES/count*100,"%")
    
        print("台湾:",TW)
        print(TW/count*100,"%")
    
        print("韓国:",KR)
        print(KR/count*100,"%")
    
        print("インド:",IN)
        print(IN/count*100,"%")
    
        print("日本",JP)
        print(JP/count*100,"%")
    
        print("その他:",others)
        print(others/count*100,"%")
    
        print(count)
    
    
    
    if __name__ == "__main__":
        main()
    

    코드 하나로 정리해 깨끗하지 않다・・・
    def 마다 각각 따로 하고, main 은 실행으로 하는 것만이 좋다.
    그 중 수정합니다. . .

    main : 다른 것을 인수 넣어 실행한다. 왠지 http_map.html 여기서 만들고 있다. . . cityplot적인 함수로서 정의해 봅시다・・・
    circle : 원형 차트 만들기
    cplot : 국가별 비율 맵 만들기(http_map1.html)
    mess : LINEnotifier에게 메시지 보내기

    check : 확인하듯이 모두 출력하고 있다



    위의 프로그램을 실행하면


    이런 느낌으로 LINE이 보내집니다.

    추가 작업 디렉토리에
    - http_map.html
    - http_map1.html
    - apache-access.png
    가 생성됩니다.

    이번에는 생성되는 장소를 작업 디렉토리로하고 있습니다만, 그다지 좋지 않다고 생각하기 때문에
    /var/log 부하나/usr/src/하하 등에 그것용의 디렉토리를 두도록 하는 편이 좋을 것 같습니다.

    그리고는 각각 두고 싶은 web 컨텐츠 디렉토리에 설치하면 좋을 뿐.

    콘텐츠 설치



    콘텐츠 디렉토리
    /var/www/html
    |--map
       |--index.html
        |--http_map1.html
       |-- log
            |-- 日付.jpg
    
    ### accessip-set
    ```accessip-set.sh
    ##!/bin/bash
    
    SAVE_DIR="/var/www/html/map"
    
    cp /root/app/accessip/apache-access.png ${SAVE_DIR}/log/`date +%Y%m%d`.jpg
    cp -f /root/bin/log/http_map.html ${SAVE_DIR}/index.html
    cp -f /root/bin/log/http_map1.html ${SAVE_DIR}/
    
    

    로그 위치나 컨텐츠 설치 위치 등은 조정해 주십시오.

    index.html(http_map.html)





    http_map1.html





    이런 느낌이 된다.

    정기 실행



    원하는대로 cron을 설정하십시오. . .
    [root@fishkiller ~]# crontab -l
    # apachelog ip
    0 5 * * * cat /var/log/httpd/access_`date +%Y%m`$((`date +%d`-1)).log|awk '{print $1}'|sort > /root/app/accessip/httpd-ip.txt
    1 5 * * * /root/.pyenv/shims/python /root/app/accessip/apacheip.py
    2 5 * * * /root/bin/apache-access.sh
    

    ※python을 cron으로 실행할 때는 풀 패스로 쓸 필요가 있습니다.
    [root@fishkiller ~]# which python
    /root/.pyenv/shims/python
    

    ⇓ 1개의 쉘에 정리해 버립니다.
    [root@fishkiller ~]# crontab -l
    # apachelog ip
    0 5 * * * /root/bin/apache-ip.sh
    

    아파체 위치 p. sh


    ##!/bin/bash
    
    cat /var/log/httpd/access_`date +%Y%m`$((`date +%d`-1)).log|awk '{print $1}'|sort > /root/app/accessip/httpd-ip.txt
    
    /root/.pyenv/shims/python /root/app/accessip/apacheip.py
    
    /root/bin/apache-access.sh
    

    같은 IP를 계산하지 않으려면
    첫 번째에 uniq를 추가하면 OK
    cat /var/log/httpd/access_`date +%Y%m`$((`date +%d`-1)).log|awk '{print $1}'|sort |uniq > /root/app/accessip/httpd-ip.txt
    

    위의 cron 설정에서는 매일 아침 5시 LINE 와 버리므로,,,
    7시 정도로 해 두면 좋을지도 모릅니다.

    요약



    이번은 apache의 로그로 했습니다만, IP의 리스트가 있으면 좋을 뿐이므로, fial2ban으로 BAN했다나 mail의 발송주등 로그로부터 IP 추출할 수 있습니다.
    예를 들어, 자신의 fail2ban의 BAN 한 IP로하면 거의 거의 중국이됩니다.




    특정 시간만 추출하면 특정 시간의 것도 할 수 있고, 다양한 응용은 효과가있을 것 같습니다.
    현재 상태, 국가를 지정하고 있는 것과 국가를 추가할 때 귀찮은 등이 향후 개선점이라고 생각합니다.

    이것을 신경쓰고, 자신의 서버에의 액세스가 어떤 곳에서 오는지 의식해 보는 것도 좋을지도 모릅니다.

    좋은 웹페이지 즐겨찾기