nmap抓取的数据的匹配问题 郝伟,刘加勇 2020/12/26

[TOC]

1. 1 需求

1.1. 1.1 概要描述

从抓取到的nmapscan.txt读取信息,然后在字典pocs.txt中进行比对,如果匹配成功,则将返回匹配到的内容的键值。

1.2. 1.2 详细描述

从nmapscan.txt中取出每一条,然后再取出每一条中的 name、product、port的值,使用nmapscan.txt中的name和product字段匹配pocs.json中的rule字段,使用nmapscan.txt的port匹配pocs.json中的port的值,如果有一个命中,则取出命中的poc名称,即key值,例如“Apache Solr_Unauthorized”,“CouchDB_Unauthorized”

1.2.1. 输入文件

输入文件只有1个文件 nmapscan.txt,其内容是多条json格式的记录(即每条就是单独的json作为一个输入),例如:

{"ip":"192.168.101.28", "ipinfo":{"ports": {"135": {"state": "open", "name": "msrpc", "product": "Microsoft Windows RPC", "version": "", "extrainfo": ""}, "139": {"state": "open", "name": "netbios-ssn", "product": "Microsoft Windows netbios-ssn", "version": "", "extrainfo": ""}, "445": {"state": "open", "name": "microsoft-ds", "product": "Microsoft Windows 2003 or 2008 microsoft-ds", "version": "", "extrainfo": ""}, "1025": {"state": "open", "name": "msrpc", "product": "Microsoft Windows RPC", "version": "", "extrainfo": ""}}, "system": "Windows", "vendor": "Microsoft"}}


{"ip":"192.168.101.29", "ipinfo":{"ports": {"22": {"state": "open", "name": "ssh", "product": "OpenSSH", "version": "8.1p1 Debian 1", "extrainfo": "protocol 2.0"}}, "system": "Linux", "vendor": "Linux"}}


{"ip":"192.168.101.26", "ipinfo":{"ports": {"22": {"state": "open", "name": "ssh", "product": "OpenSSH", "version": "7.6p1 Ubuntu 4ubuntu0.3", "extrainfo": "Ubuntu Linux; protocol 2.0"}, "5000": {"state": "open", "name": "http", "product": "Tornado httpd", "version": "6.1", "extrainfo": ""}, "5001": {"state": "open", "name": "http", "product": "BaseHTTPServer", "version": "0.6", "extrainfo": "Python 4.8.5"}, "9999": {"state": "open", "name": "http", "product": "Tornado httpd", "version": "6.1", "extrainfo": ""}, "27017": {"state": "open", "name": "mongodb", "product": "MongoDB", "version": "4.2.3", "extrainfo": ""}}, "system": "Linux", "vendor": "Linux"}}

提供一个在线工具:可以对json字符串进行格式这里

1.2.2. 字典文件

pocs.json,是要匹配的目标

{
    "Apache Solr_Unauthorized": {
        "rule": "Solr,Apache Solr",
        "port": "8983",
        "path": "component/apache solr/Apache Solr_Unauthorized.un",
        "create": "system"
    },
    "CouchDB_Unauthorized": {
        "rule": "CouchDB",
        "port": "5984",
        "path": "component/couchdb/CouchDB_Unauthorized.un",
        "create": "system"
    },
    "Docker_Unauthorized": {
        "rule": "docker",
        "port": "2375",
        "path": "component/docker/Docker_Unauthorized.un",
        "create": "system"
    },
    "Domino_Unauthorized": {
        "rule": "domino",
        "port": "1352",
        "path": "component/domino/Domino_Unauthorized.un",
        "create": "system"
    },
    "Elastic_Unauthorized": {
        "rule": "ElasticSearch",
        "port": "9200",
        "path": "component/elasticsearch/Elastic_Unauthorized.un",
        "create": "system"
    },
    "Esccms_Unauthorized": {
        "rule": "esccms",
        "port": "80",
        "path": "cms/esccms/Esccms_Unauthorized.un",
        "create": "system"
    },

1.2.3. 处理要求

在匹配时,要求尽可能多和精准的匹配,请看以下三个示例:

1.3. 1.3 示例

1.3.1. 示例1

"1521": {
        "state": "open",
        "name": "oracle-tns",
        "product": "Oracle TNS listener",
        "version": "11.2.0.2.0",
        "extrainfo": "unauthorized"

如果按照name和product匹配:匹配的结果是:Oracle-GlassFish-Server-Open-Source-Edition_DirectTraver 
但是查看pocs.json文件中Oracle-GlassFish-Server-Open-Source-Edition_DirectTraver的信息,会发现它的端口是80,不是1521,所以最终的匹配结果是空,如果匹配到Oracle-GlassFish-Server-Open-Source-Edition_DirectTraver,就是不精准

1.3.2. 示例2

"389": {
        "state": "open",
        "name": "ldap",
        "product": "Microsoft Windows Active Directory LDAP",
        "version": "",
        "extrainfo": "Domain: huaun.com, Site: Default-First-Site-Name"
      },

  最后的匹配结果:是pocs.json中的“LDAP_Unauthorized”,不含windows相关的poc

1.3.3. 示例3

      "8009": {
        "state": "open",
        "name": "ajp13",
        "product": "Apache Jserv",
        "version": "",
        "extrainfo": "Protocol v1.3"
      },

最终匹配的结果是pocs.json中的“Apache-Tomcat-Ajp_File-Read”

总结:由于nmapcan.txt的内容会随着用户环境的变化而变化,后面我会尽量多的提供,pocscan.json基本是不变的。

2. 2 问题描述

在程序执行正常的情况下,能够命中的匹配过低,几乎匹配不到几条记录。

3. 3 问题分析

首先,从pocs.txt和nmapscan.txt中读取关键要匹配的内容,即

  • poscs.json (下载) 提取 key, name 和 rule 信息;
  • nmapscan.txt (下载) 提取 port, name 和 product 信息.

通过附录1的代码,对两个示例文件进行提取,可以获得数据如上(注 pocs的key和nmap中的port未打印输出):

****************************** pocs: rule (20 of 817) ****************************** 
Solr,Apache Solr
CouchDB
docker
domino
ElasticSearch
esccms
Hadoop
influxDB
jboss
jenkins
joomla
mongodb
Redis
rsync
Zookeeper
Memcached
activemq
axis
cerio
gtafana
****************************** nmap: name, product (total: 5) ******************************
ssh, OpenSSH
http, Tornado httpd
http, BaseHTTPServer
http, Tornado httpd
mongodb, MongoDB

经观察可以发现,两者在匹配时,由于字符串不完全相等,导致难以匹配成功。比如第24行,product的内容为Tornado httpd,是两个单词的组合,而对于第25行,内容为BaseHTTPServer,所以在匹配时其内容无法与字典中的内容完全相等,从而导致匹配失败。

4. 4 解决方案

根据以上情况,提出了两种解决方案如下。

4.1. 4.1 拆分匹配法

核心思想是将输入内容拆分成单词列表,然后进行逐一匹配,具体步骤如下:

  • 第1步 拆分 对于使用分隔符,包括空格、横杠、括号等将输入字符串拆分成多个单词;
  • 第2步 过滤 对于一些不需要匹配的内容,可以提前过滤,比如httpd。具体的实现方法是建立一个过滤词列表,把不需要的词都放在这个列表中,然后在匹配时,只要输入的内容包括在过滤词列表中,就直接过滤掉
  • 第3步 关键字匹配 经过滤后,剩下的关键字再在pocs字典中进行匹配,只要有一个单词匹配成功,就表示匹配完成,返回最终匹配的结果。

4.2. 4.2 相似度匹配法

我们还可以使用字符串相似度计算的方法在匹配时,对两个不完全一样的字符串进行匹配,根据返回的相似度值,设定一个阀值,选择超过阀值的匹配。字符串相似度计算有很多方法,以下给出两种方法进行参考:

4.2.1. 4.2.1 杰卡德相似系数评估方法

(理论部分:略)

def jsc(str1, str2):
    '''
    :type str1: 第1个字符串
    :type str2: 第2个字符串
    :rtype: float 两者的相似度,值在[0,1]区间上
    '''
    # 统计所有出现的字符数量
    all_chars = []
    for ch in str1:
        if ch not in all_chars:
            all_chars.append(ch)
    for ch in str2:
        if ch not in all_chars:
            all_chars.append(ch)

    # 统计公共的字符数量
    common_chars = []
    for ch in all_chars:
        if ch in str1 and ch in str2:
            common_chars.append(ch)

    # 返回两者的比值作为相似度    
    return 1. * len(common_chars) / len(all_chars)

调用方法

str1 = "hello"
strs = ["hello", " hello", "hello1", " hello1","hel1lo", ""]
for str2 in strs:
    print(str1 + " : " + str2, " = ", jsc(str1, str2))

执行以后可以得到以下的结果:

hello : hello  =  1.0
hello :  hello  =  0.8
hello : hello1  =  0.8
hello :  hello1  =  0.6666666666666666
hello : hel1lo  =  0.8
hello :   =  0.0

4.2.2. 4.2.2 编辑距离算法

(理论部分:略) 直接上代码:

直接上代码:

def jdcompare(str1, str2):
    '''
    :type str1: 第1个字符串
    :type str2: 第2个字符串
    :rtype: float 两者的相似度,值在[0,1]区间上
    '''
    m = len(str1) # 第1个字符串长度
    n = len(str2) # 第2个字符串长度

    # 如果有任意一个长度为0,则相似度为0.
    if(m == 0):
       return 0.
    if(n == 0):
       return 0.

    # 初始化矩阵
    matrix = [[i + j for j in range(n + 1)] for i in range(m + 1)]

    # 根据第3步的算法进行两层循环计算
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            t = 0 if str1[i - 1] == str2[j - 1] else 1
            v1 = matrix[i - 1][j] + 1
            v2 = matrix[i][j - 1] + 1
            v3 = matrix[i - 1][j - 1] + t
            matrix[i][j] = min(v1, v2, v3)

    # 结果进行了计算,返回的不再是距离,而是相似度
    return 1.0 - 1.0 * matrix[len(str1)][len(str2)] / max(len(str1), len(str2))

以下是使用和效果

str1 = "hello"
strs = ["hello", " hello", "hello1", " hello1","hel1lo", ""]
for str2 in strs:
    print(str1 + " : " + str2, " = ", compare(str1, str2))

执行以后可以得到以下的结果:

hello : hello  =  1.0
hello :  hello  =  0.8333333333333334
hello : hello1  =  0.8333333333333334
hello :  hello1  =  0.7142857142857143
hello : hel1lo  =  0.8333333333333334
hello :   =  0.0

4.2.3. 4.2.3 difflib库

此库也有比较功能,示例如下:

import difflib as df
def compare(s1, s2):
    return df.SequenceMatcher(None, s1, s2).quick_ratio()
print(string_compare("hello", "hellO1"))
# 输出为:
# 0.7272727272727273

使用相同的代码如下:

str1 = "hello"
strs = ["hello", " hello", "hello1", " hello1","hel1lo", ""]
for str2 in strs:
    print(str1 + " : " + str2, " = ", compare(str1, str2))
执行以后可以得到以下的结果:
hello : hello  =  1.0
hello :  hello  =  0.9090909090909091
hello : hello1  =  0.9090909090909091
hello :  hello1  =  0.8333333333333334
hello : hel1lo  =  0.9090909090909091
hello :   =  0.0

4.3. 4.3 其他

在分词时,有时候会有一些无分隔词的情况,比如BaseHTTPServer。通常有两种解决方法:基于字典的和基于自然语言处理的。由于第二种方法比较麻烦,所以推荐使用基于字典的方法。基于字典的方法的原理是这样:建立一个字典,里面有一些常见的合在一起的拼写,只要有就根据字符进行拆分。

举例来说,字典为 dic={'Base', ...},那么在处理BaseHTTPServer时,就会提取到Base,然后剩余的内容就变成了 HTTPServer,如果字典中还有HTTPServer,同样进行提取。

5. 5 问题反馈

5.1. 2020/12/28 测试数据

根据测试显示,单一的通过方案2的相似度估算,有时候结果不正确。根据刘加勇的测试结果: 使用以下数据测试时,Microsoft IIS httpdIIS 并不能匹配最高。

str1s = ["Microsoft IIS httpd", "Oracle WebLogic Server"]
str2s = ["WebLogic-Deserialization_RCE(CVE-2019-2725)",
        "Weblogic-XMLDecoder_RCE",
        "Weblogic-WLS_RCE(CVE-2020-2551)",
        "Weblogic-Wls9_RCE(CVE-2019-2729)",
        "Weblogic-WLS_RCE(CVE-2018-2893)",
        "Weblogic-WLS_RCE(CVE-2018-2628)",
        "Weblogic-WLS-Core_RCE",
        "Weblogic_SSRF",
        "IIS"]

5.1.1. 1、 杰卡德方法方法数据

/Users/yong/Projects/tensorflow/venv/bin/python /Users/yong/Projects/test/杰卡德相似系数评估方法.py
Microsoft IIS httpd : WebLogic-Deserialization_RCE(CVE-2019-2725)  =  0.15789473684210525
Microsoft IIS httpd : Weblogic-XMLDecoder_RCE  =  0.2222222222222222
Microsoft IIS httpd : Weblogic-WLS_RCE(CVE-2020-2551)  =  0.125
Microsoft IIS httpd : Weblogic-Wls9_RCE(CVE-2019-2729)  =  0.125
Microsoft IIS httpd : Weblogic-WLS_RCE(CVE-2018-2893)  =  0.11764705882352941
Microsoft IIS httpd : Weblogic-WLS_RCE(CVE-2018-2628)  =  0.12121212121212122
Microsoft IIS httpd : Weblogic-WLS-Core_RCE  =  0.2
Microsoft IIS httpd : Weblogic_SSRF  =  0.18181818181818182
Microsoft IIS httpd : IIS  =  0.14285714285714285
Oracle WebLogic Server : WebLogic-Deserialization_RCE(CVE-2019-2725)  =  0.3235294117647059
Oracle WebLogic Server : Weblogic-XMLDecoder_RCE  =  0.4166666666666667
Oracle WebLogic Server : Weblogic-WLS_RCE(CVE-2020-2551)  =  0.37037037037037035
Oracle WebLogic Server : Weblogic-Wls9_RCE(CVE-2019-2729)  =  0.27586206896551724
Oracle WebLogic Server : Weblogic-WLS_RCE(CVE-2018-2893)  =  0.3448275862068966
Oracle WebLogic Server : Weblogic-WLS_RCE(CVE-2018-2628)  =  0.35714285714285715
Oracle WebLogic Server : Weblogic-WLS-Core_RCE  =  0.55
Oracle WebLogic Server : Weblogic_SSRF  =  0.5
Oracle WebLogic Server : IIS  =  0.0625

Process finished with exit code 0

5.1.2. 2、编辑距离算法方法数据

/Users/yong/Projects/tensorflow/venv/bin/python /Users/yong/Projects/test/编辑距离算法.py
Microsoft IIS httpd : WebLogic-Deserialization_RCE(CVE-2019-2725)  =  0.09302325581395354
Microsoft IIS httpd : Weblogic-XMLDecoder_RCE  =  0.04347826086956519
Microsoft IIS httpd : Weblogic-WLS_RCE(CVE-2020-2551)  =  0.06451612903225812
Microsoft IIS httpd : Weblogic-Wls9_RCE(CVE-2019-2729)  =  0.09375
Microsoft IIS httpd : Weblogic-WLS_RCE(CVE-2018-2893)  =  0.06451612903225812
Microsoft IIS httpd : Weblogic-WLS_RCE(CVE-2018-2628)  =  0.06451612903225812
Microsoft IIS httpd : Weblogic-WLS-Core_RCE  =  0.04761904761904767
Microsoft IIS httpd : Weblogic_SSRF  =  0.10526315789473684
Microsoft IIS httpd : IIS  =  0.1578947368421053
Oracle WebLogic Server : WebLogic-Deserialization_RCE(CVE-2019-2725)  =  0.06976744186046513
Oracle WebLogic Server : Weblogic-XMLDecoder_RCE  =  0.13043478260869568
Oracle WebLogic Server : Weblogic-WLS_RCE(CVE-2020-2551)  =  0.032258064516129004
Oracle WebLogic Server : Weblogic-Wls9_RCE(CVE-2019-2729)  =  0.0625
Oracle WebLogic Server : Weblogic-WLS_RCE(CVE-2018-2893)  =  0.032258064516129004
Oracle WebLogic Server : Weblogic-WLS_RCE(CVE-2018-2628)  =  0.032258064516129004
Oracle WebLogic Server : Weblogic-WLS-Core_RCE  =  0.13636363636363635
Oracle WebLogic Server : Weblogic_SSRF  =  0.36363636363636365
Oracle WebLogic Server : IIS  =  0.045454545454545414

Process finished with exit code 0

5.1.3. 3、difflib方法数据

/Users/yong/Projects/tensorflow/venv/bin/python /Users/yong/Projects/test/difflib库.py
Microsoft IIS httpd : WebLogic-Deserialization_RCE(CVE-2019-2725)  =  0.22580645161290322
Microsoft IIS httpd : Weblogic-XMLDecoder_RCE  =  0.3333333333333333
Microsoft IIS httpd : Weblogic-WLS_RCE(CVE-2020-2551)  =  0.16
Microsoft IIS httpd : Weblogic-Wls9_RCE(CVE-2019-2729)  =  0.1568627450980392
Microsoft IIS httpd : Weblogic-WLS_RCE(CVE-2018-2893)  =  0.16
Microsoft IIS httpd : Weblogic-WLS_RCE(CVE-2018-2628)  =  0.16
Microsoft IIS httpd : Weblogic-WLS-Core_RCE  =  0.3
Microsoft IIS httpd : Weblogic_SSRF  =  0.25
Microsoft IIS httpd : IIS  =  0.2727272727272727
Oracle WebLogic Server : WebLogic-Deserialization_RCE(CVE-2019-2725)  =  0.4
Oracle WebLogic Server : Weblogic-XMLDecoder_RCE  =  0.5777777777777777
Oracle WebLogic Server : Weblogic-WLS_RCE(CVE-2020-2551)  =  0.37735849056603776
Oracle WebLogic Server : Weblogic-Wls9_RCE(CVE-2019-2729)  =  0.2962962962962963
Oracle WebLogic Server : Weblogic-WLS_RCE(CVE-2018-2893)  =  0.37735849056603776
Oracle WebLogic Server : Weblogic-WLS_RCE(CVE-2018-2628)  =  0.37735849056603776
Oracle WebLogic Server : Weblogic-WLS-Core_RCE  =  0.5581395348837209
Oracle WebLogic Server : Weblogic_SSRF  =  0.5142857142857142
Oracle WebLogic Server : IIS  =  0.08

Process finished with exit code 0

三种方法都未能预期,实际上即使修改算法,也未必保保证一定完成任务。造成这一问题的根本原因是,出现的情况很多,有规则的也有无规则的文本,所以不能使用一种算法解决所有的问题。所以,针对这个问题,建议采取多种算法结合的方式,如先使用正则表达式和方案一,解决一些规则的数据,然后再通过方案二的相似度计算,解决剩下的问题。可以在一定程度上解决问题。

6. 附录

6.1. 附1:数据提取源代码

本段代码主要功能是从 pocs.json和nmap.txt文件中提取指定的内容,其中第10-17行用于返回文件中所有行,27-30提取procs中的所有port,key和rule,32-39行提取nmap中的所有内容,最后在第41-46行进行输出。

```python{.line-numbers, highlight=[10-17, 27-30, 32-39]}

7. -- coding: utf-8 --

""" Created on Fri Dec 25 21:31:52 2020 @author: hao """

import json

def read_lines(filepath): ''' loads and returns all text from a file. ''' lines = [] with open(filepath, 'r', encoding='utf-8') as f: lines = f.readlines() return lines

8. file1 and file2 store poc data and nmap data, respectively.

file1=r'd:\data\pocs.json' file2=r'd:\data\nmapscan.txt'

9. read and construct a json data

pocs=json.loads(''.join(read_lines(file1)))

10. represnts pros.json in json

poc_rules=[] for item in pocs: data1 = pocs[item] poc_rules.append((data1['port'], data1['rule'])) # port, key, rule

11. represents the lineno-th line of nmap file in json

lineno = 3
nmaps_ports = json.loads(read_lines(file2)[lineno])['ipinfo']['ports']

12. retreives port, name and product from "nmap.ipinfo.ports"

nmaps_name_product = [] for item in nmaps_ports: # item is port, use 'name' & 'product' to meet 'rule' nmaps_name_product.append((item, nmaps_ports[item]['name'], nmaps_ports[item]['product']))

13. show two types of data

showcount=20 # print first showcount of the total thousands of lines data print('{0} pocs: rule ({1} of {2}) {0} '.format('' 30, showcount, len(poc_rules))) for item in poc_rules[0:showcount]: print(item[1]) print('{0} nmap: name, product (total: {1}) {0}'.format('' 30, len(nmaps_name_product))) for item in nmaps_name_product: print(item[1] + ', ' + item[2]) ```

results matching ""

    No results matching ""