「VirusTotal」でドメインの健全性を定期的に自動でチェックする(APIv3版)

2020.05.17

はじめに

こんにちは。大阪オフィスの林です。

先日、下記のブログを書かせて頂きました。

前回はVirusTotalから提供されているAPIv2を使って実装していたのですが、APIv2だとドメインによっては結果がイイ感じに取得できなさそうだったので、同様にVirusTotalから提供されているAPIv3を使って同じようなチェックをやってみました。

何がイイ感じに取れなかった???

下記はAPIv2のドメインチェック実行結果の抜粋なのですが、かなり前の日時(下記の例だと2014年のレポート)まで引っ張って来てしまい、チェックしたいドメインに対して最新のレポートだけでチェックすることが困難ということが分かりました。(最新のレポートだけをチェックするという機能が無く、APIv2でやるとなると結構作りこみが手間ということが分かりました。。。)

(省略) 
    "detected_urls": [
        {
            "url": "http://classmethod.net/index.php?email=jonas.hermanson@sweroad.se&method=post",
            "positives": 2,
            "total": 52,
            "scan_date": "2014-05-26 08:28:36"
        }
    ],
(省略) 

なお、WebUIでも同様のドメインチェックの機能が提供されているのですがWebUIからチェックを試すと、問題なし(Positivesが「0」)と判定されます。(APIv2は過去のログも見てしまうため、上記のレポートのように「Positivesが0以外という判定をしてしまいます。」)

なぜ結果が異なるかというと、WebUIで実行した際にキックされるAPIはAPIv3であり、そもそもの得られる結果や機能が違うからです。APIv3は現在(2020年5月17日現在)Bata版という扱いですが、制約や制限などはAPIv2と変わりが無く、VirusTotalではAPIv3の使用が推奨されているということが分かったため、本記事ではAPIv3での実装方法をまとめておきたいと思います。

Lambdaのコード作成(v2とv3との違い)

今回も「python3.7」を使ってコードを作成していきます。コードの全体は本記事の最後に載せておきますので、本項ではポイントとなる部分だけ説明します。

・使用したライブラリ(v2とv3との違い)

APIv2で実装していた時と比較し、コマンド実行のためにimport subprocessfrom subprocess import check_outputを追加しています。

import os
#↓の2行を追加してる。
import subprocess
from subprocess import check_output
import json
import boto3
import logging
import requests
import re
import time
from pytz import timezone
from datetime import datetime

・環境変数の利用(v2とv3との違い)

APIv2APIv3で変わりはありません。

・ドメインのリストを取得(v2とv3との違い)

APIv2APIv3で変わりはありません。

・APIキック&レポートを取得(v2とv3との違い)

前回の記事では実行回数にフォーカスして説明をしておりましたが、実行するAPIのURLがhttps://www.virustotal.com/vtapi/v2/domain/reportからhttps://www.virustotal.com/api/v3/domains/に変わっています。

・レポートのチェック&異常時通知(v2とv3との違い)

前回の記事ではリクエストの失敗positivesの値で異常の判定をしていました。

if '"response_code": 1' in response.text:
     if re.search('"positives": [^0]' , response.text):
        response = client.publish(
           TopicArn = snstopic,
           Message = json.dumps(j, indent=4),
           Subject = 'ドメイン名「' + domain + '」は怪しい'
        )
        key = 'Log/' + Shapjst_now  + '/BadDomain/' + domain + '.log'
     else:
        key = 'Log/' + Shapjst_now  + '/GoodDomain/' + domain + '.log'
else:
    (処理)

APIv3では、last_analysis_statsmaliciousを見て異常の判定を行うこととなります。なおmaliciousの結果が上述したWebUIで見えた結果とリンクする数値となります。

if re.search('"last_analysis_stats"' , jsonresult):
   if re.search('"malicious": [^0]' , jsonresult):
      response = client.publish(
         TopicArn = snstopic,
         Message = jsonresult,
         Subject = 'ドメイン名「' + domain + '」は怪しい'
      )
      key = 'Log/' + Shapjst_now  + '/BadDomain/' + domain + '.log'
   else:
      key = 'Log/' + Shapjst_now  + '/GoodDomain/' + domain + '.log'
else:

・ログを格納

APIv2APIv3で変わりはありません。

まとめ

APIv2APIv3で得られる結果や実装方法が異なることが分かりました。またドメインチェックに関しては簡単に本記事で簡単に実装方法を紹介させて頂きました。今回は自分の備忘録的なまとめになりましたが何かしら参考になると幸いです。

以上、大阪オフィスの林がお送りしました!

コード

import os
import subprocess
from subprocess import check_output
import json
import boto3
import logging
import requests
import re
import time
from pytz import timezone
from datetime import datetime

def lambda_handler(event, context):
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    apikey = os.environ['apikey']
    snstopic = os.environ['snstopic']
    bucket_name = os.environ['bucket_name']
    domainlist = os.environ['domainlist']
    
    s3 = boto3.client('s3')
    
    response = s3.get_object(Bucket=bucket_name, Key=domainlist)
    body = response['Body'].read()
    
    client = boto3.client('sns')
            
    bodystr = body.decode('utf-8')
    domains = bodystr.split("\n")

    i = 1

    s3 = boto3.resource('s3') 
    utc_now = datetime.now(timezone('UTC'))
    jst_now = utc_now.astimezone(timezone('Asia/Tokyo'))
    Shapjst_now = jst_now.strftime('%Y-%m-%d-%H-%M')
    
    for domain in domains:
        if i % 4 != 0:
            domain = domain.replace('\r', '')
            result = check_output(["curl","-s","https://www.virustotal.com/api/v3/domains/"+domain,"-H","x-apikey:"+apikey])
            jsonresult = result.decode('utf8').replace("'", '"')
            if re.search('"last_analysis_stats"' , jsonresult):
                if re.search('"malicious": [^0]' , jsonresult):
                    response = client.publish(
                        TopicArn = snstopic,
                        Message = jsonresult,
                        Subject = 'ドメイン名「' + domain + '」は怪しい'
                    )
                    key = 'Log/' + Shapjst_now  + '/BadDomain/' + domain + '.log'
                else:
                    key = 'Log/' + Shapjst_now  + '/GoodDomain/' + domain + '.log'
            else:
                response = client.publish(
                    TopicArn = snstopic,
                    Message = jsonresult,
                    Subject = 'ドメイン名「' + domain + '」はリクエスト失敗'
                )
                key = 'Log/' + Shapjst_now  + '/RequestfailedDomain/' + domain + '.log'
            obj = s3.Object(bucket_name,key)
            obj.put( Body = jsonresult )
            i = i + 1
        else:
            domain = domain.replace('\r', '')
            result = check_output(["curl","-s","https://www.virustotal.com/api/v3/domains/"+domain,"-H","x-apikey:"+apikey])
            jsonresult = result.decode('utf8').replace("'", '"')
            if re.search('"last_analysis_stats"' , jsonresult):
                if re.search('"malicious": [^0]' , jsonresult):
                    response = client.publish(
                        TopicArn = snstopic,
                        Message = jsonresult,
                        Subject = 'ドメイン名「' + domain + '」は怪しい'
                    )
                    key = 'Log/' + Shapjst_now  + '/BadDomain/' + domain + '.log'
                else:
                    key = 'Log/' + Shapjst_now  + '/GoodDomain/' + domain + '.log'
            else:
                response = client.publish(
                    TopicArn = snstopic,
                    Message = jsonresult,
                    Subject = 'ドメイン名「' + domain + '」はリクエスト失敗'
                )
                key = 'Log/' + Shapjst_now  + '/RequestfailedDomain/' + domain + '.log'
            obj = s3.Object(bucket_name,key)
            obj.put( Body = jsonresult )
            i = i + 1
            time.sleep(60)