Analysis of a crawler file

Analysis of a crawler file

Recently, the lab senior assigned a task to integrate various CVE information to form an attack graph. The following code is the first step. The purpose is to crawl the four target information of CVE from two websites through a python crawler and store it in the mongodb database.

Source code

# Script to web scrape vulnerability information into mongodb

from bs4 import BeautifulSoup
from pymongo import MongoClient
from urllib.request import Request, urlopen
import csv
import sys
import re

client = MongoClient('mongodb://localhost:27017/')
db = client.project #project是一个自定义的数据库。
vulns = db.vulnerabilities #vulnerabilities是db中的一个数据表
vulns.drop()  #删除数据表中的集合【删除之前,重新爬取最新的数据】

mapping1 = { 'High': 2, 'Low': 1, 'None': 0 } #数据结构——字典
mapping2 = { 'Admin': 2, 'User': 1, 'None': 0 }

filename=sys.argv[1]  #在命令行中输入的第二个文件,第一个文件就是这个py文件本身
print("Current file:\t", sys.argv[1])

try:
    with open(filename) as csv_file:  #with语句打开文件不用手动关闭文件,提高了代码的优雅性
        csv_reader = csv.reader(csv_file) #调用csv库中的reader方法得到一个遍历csv文件各行的读取器对象
        for row in csv_reader: #利用for循环把csv文件每行的内容读取下来[每行返回的是一个列表]
            CVE = row[0]  #行列表的第一个,因为每行只有一个文件,所以返回的值就是那个CSV的名称
            print(CVE)
            url = "https://www.cvedetails.com/cve/" + CVE
            req = Request(url, headers={ 'User-Agent': 'Mozilla/5.0' })
            html_doc = urlopen(req).read()
            soup = BeautifulSoup(html_doc, 'lxml')  #BeautifulSoup使得按照标签、id、class来寻找内容成为可能
            table = soup.find("table", { 'id': 'cvssscorestable', 'class': 'details' })
            field_row = table.findAll("tr")[6]
            field_value = field_row.find("span").string
            gained_access = mapping2[field_value]

            url = "https://nvd.nist.gov/vuln/detail/" + CVE
            html_doc = urlopen(url).read()
            soup = BeautifulSoup(html_doc, 'lxml')

            required_priv_string = re.findall("vuln-cvssv3-pr\&\#39\;\&gt\;\s(.*?)\s\&lt.*",html_doc.decode()) #re是正则模块,引号内的内容就是正则语句,注意html_doc.decode()的内容和soup的内容略有不同
            if len(required_priv_string) != 0:
                field_value = required_priv_string[0]
            else:
                field_value = 'None'

            required_priv = mapping1[field_value]

            attack_vectors = re.findall("vuln-cvssv2-av\&\#39\;\&gt\;(.*?)\&lt.*",html_doc.decode())
            attack_vector = attack_vectors[0]

            # Add entry
            document = {}
            document['cveName'] = CVE
            document['gained_access'] = gained_access
            document['required_priv'] = required_priv
            document['access_vector'] = attack_vector

            #print(document)
            vulns.insert_one(document)

    print("Successfully imported CVE details")

except IOError:
    print("File {} does not exist".format(filename))
    exit()

pymongo module

This python module is used to connect to the local mongodb.

from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017/')
db = client.project 
vulns = db.vulnerabilities
vulns.drop()  

We then initialize a MongoClient can be used .to select the database, the database then .what is the data table. Various operations of the data table are also passed .; very intuitive and easy to use. It should be noted here that when we use client.databaseto select a database, if the database does not exist, the database will not be created immediately after we call this way, only the database has a table, and after inserting a collection into the table, this The database will be created.

sys module

sys.argv属性会以列表方式返回我们在python3命令后输入的文件名称。其中sys.argv[0]自然就是我们运行的python文件名。

➜  ~ cat test.py         
import sys
print(sys.argv)%                                                                 
➜  ~ python3 test.py fuck
['test.py', 'fuck']

python with语句

with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭/线程中锁的自动获取和释放等。

我们可以利用with语句来打开一个文件,这样我们就不用手动来关闭文件,提高了代码的优雅度。

with open(filename) as f: 
    dosomething

同时我们可以用as来给这个文件句柄取一个好听的别名。

csv库

之前只是听说过CSV这个词,今天更加深入地了解了一下。

逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符序列,不含必须像二进制数字)那样被解读的数据。

这个爬虫文件用到了这样一个csv文件。

Insert picture description here

我们在之后的程序中会爬取这些CVE的信息,那python是如何处理CSV文件的呢?

这里利用到了csv这个库。

csv_reader = csv.reader(csv_file) #调用csv库中的reader方法得到一个遍历csv文件各行的读取器对象
    for row in csv_reader: #利用for循环把csv文件每行的内容读取下来[每行返回的是一个列表]
        CVE = row[0]  #行列表的第一个,因为每行只有一个文件,所以返回的值就是那个CSV的名称
        print(CVE)

urllib.request库中Request和urlopen

from urllib.request import Request, urlopen

url = "https://www.cvedetails.com/cve/CVE-2021-31760"
req = Request(url, headers={ 'User-Agent': 'Mozilla/5.0' })
html_doc = urlopen(req).read()

我们来看一看html_doc会返回怎么样的内容。

Insert picture description here

经过vscode的格式化之后,我们发现它和普通的html文件差不多,但是有着许多\r\n\t等换行符。

我们可以通过输出html_doc.decode()来消除掉这些换行符,代码和结果如下

from urllib.request import Request, urlopen

url = "https://www.cvedetails.com/cve/CVE-2021-31760"
req = Request(url, headers={ 'User-Agent': 'Mozilla/5.0' })
html_doc = urlopen(req).read()
with open('3.html', "w") as f:
  f.write(str(html_doc.decode()))
Insert picture description here

bs4库中的BeautifulSoup

我们看到利用urllib.request里面的Request和urlopen已经爬到了网页的源代码。那我们为什么还需要BeautifulSoup这个工具呢?原因在于我们用它可以方便得利用标签、id、class来进行信息的检索。

soup = BeautifulSoup(html_doc, 'lxml') #选择lxml解析器
table = soup.find("table", { 'id': 'cvssscorestable', 'class': 'details' })#找到id为cvs...;class为details的table标签
field_row = table.findAll("tr")[6] #在这个标签中找到第七个tr标签
field_value = field_row.find("span").string	#得到这个tr标签中的span标签的值

re正则库

 required_priv_string = re.findall("vuln-cvssv3-pr\&\#39\;\&gt\;\s(.*?)\s\&lt.*",html_doc.decode())

利用re可以实现正则匹配。

Insert picture description here

format格式函数

print("File {} does not exist".format(filename))

实现了类似C中%s的操作。python中用{}来代替。

➜  ~ cat test.py
print('{} yyds!'.format('wuuconix'))%                                            
➜  ~ python3 test.py
wuuconix yyds!