使用Python调用Nessus 接口实现自动化扫描
转自 https://www.jianshu.com/p/a4236809e11a
之前在项目中需要接入nessus扫描器,研究了一下nessus的api,现在将自己的成果分享出来。
Nessus提供了丰富的二次开发接口,无论是接入其他系统还是自己实现自动化扫描,都十分方便。
同时Nessus也提供了完备的API文档,可以在 Settings->My Account->API Keys->API documentation
认证
nessus提供两种认证方式,第一种采用常规的登录后获取token的方式,在https://localhost:8834/api#/resources/session条目中可以找到这种方式,它的接口定义如下:
POST /session
{
"username":{string},
"password":{string}
}
输入正确的用户名和密码,登录成功后会返回一个token
···
{
"token": {string}
}
···
在后续请求中,将token放入请求头信息中请求头的key为X-Cookie,值为 token=xxxx,例如 :X-Cookie: token=5fa3d3fd97edcf40a41bb4dbdfd0b470ba45dde04ebc37f8;
,下面是获取任务列表的例子
import requests
import json
def get_token(ip, port, username, password):
url = "https://{0}:{1}/session".format(ip, port)
post_data = {
'username': username,
'password': password
}
respon = requests.post(url, data=post_data, verify=False)
if response.status_code == 200:
data = json.loads(response.text)
return data["token"]
def get_scan_list()
# 这里ip和port可以从配置文件中读取或者从数据库读取,这里我省略了获取这些配置值得操作
url = "https://{ip}:{port}/scans".format(ip, port)
token = get_token(ip, port, username, password)
if token:
header = {
"X-Cookie":"token={0}".format(token),
"Content-Type":"application/json"
}
response = requests.get(url, headers=header, verify=False)
if response.status_code == 200:
result = json.loads(respon.text)
return result
第二种方式是使用Nessus生成的API Key,这里我们可以依次点击 Settings->My Account->API Keys-->Generate按钮,生成一个key,后续使用时填入头信息中,还是以获取扫描任务列表作为例子
def get_scan_list()
accessKey = "XXXXXX" #此处填入真实的内容
secretKey = "XXXXXX" #此处填入真实内容
url = "https://{ip}:{port}/scans".format(ip, port)
token = get_token(ip, port, username, password)
if token:
header = {
'X-ApiKeys': 'accessKey={accesskey};secretKey={secretkey}'.format(accesskey=accessKey, secretkey=secretKey)
"Content-Type":"application/json"
}
response = requests.get(url, headers=header, verify=False)
if response.status_code == 200:
result = json.loads(respon.text)
return result
对比来看使用第二种明显方便一些,因此后续例子都采用第二种方式来呈现
策略模板配置
策略模板的接口文档在 https://localhost:8834/api#/resources/policies 中。
创建策略模板
创建策略模板使用的是 策略模板的create接口,它里面有一个必须填写的参数 uuid
这个参数是一个uuid值,表示以哪种现有模板进行创建。在创建之前需要先获取系统中可用的模板。获取的接口是 /editor/{type}/templates
,type 可以选填policy或者scan。这里我们填policy
一般我们都是使用模板中的 Advanced
来创建,如下图
下面是获取该模板uuid的方法,主要思路是获取系统中所有模板,然后根据模板名称返回对应的uuid值
def get_nessus_template_uuid(ip, port, template_name = "advanced"):
header = {
'X-ApiKeys': 'accessKey={accesskey};secretKey={secretkey}'.format(accesskey=accesskey,
secretkey=secretkey),
'Content-type': 'application/json',
'Accept': 'text/plain'}
api = "https://{ip}:{port}/editor/scan/templates".format(ip=ip, port=port)
response = requests.get(api, headers=header, verify=False)
templates = json.loads(response.text)['templates']
for template in templates:
if template['name'] == template_name:
return template['uuid']
return None
有了这个id之后,下面来创建策略模板,这个接口的参数较多,但是很多参数都是选填项。
这个部分文档写的很简陋,很多参数不知道是干嘛用的,当时我为了搞清楚每个参数的作用,一个个的尝试,然后去界面上看它的效果,最后终于把我感兴趣的给弄明白了。
它的主体部分如下:
{
"uuid": {template_uuid},
"audits": {
"feed": {
"add": [
{
"id": {audit_id},
"variables": {
"1": {audit_variable_value},
"2": {audit_variable_value},
"3": {audit_variable_value}
}
}
]
}
},
"credentials": {
"add": {
{credential_category}: {
{credential_name}: [
{
{credential_input_name}: {string}
}
]
}
}
},
"plugins": {
{plugin_family_name}: {
"status": {string},
"individual": {
{plugin_id}: {string}
}
}
},
"scap": {
"add": {
{scap_category}: [
{
{scap_input_name}: {string}
}
]
}
},
"settings": {
"acls": [
permission Resource
],
//其他的减值对,这里我将他们都省略了
}
他们与界面上配置的几个大项有对应关系,能对应的上的我给做了标记,但是有的部分对应不上。
settings 是给策略模板做基础配置的,包括配置扫描的端口范围,服务检测范围等等。
credentials 是配置登录扫描的,主要包括 windows、ssh、telnet等等
plugins 配置扫描使用的插件,例如服务扫描版本漏洞等等
在settings中,对应关系如下图所示
下面是创建扫描策略模板的实际例子:
def create_template(ip, port, **kwargs): # kwargs 作为可选参数,用来配置settings和其他项
header = {
"X-ApiKeys": "accessKey={accesskey};secretKey={secretkey}".format(accesskey=accesskey,
secretkey=secretkey),
"Content-Type": "application/json",
"Accept": "text/plain"
}
policys = {}
# 这里 grouppolicy_set 存储的是策略模板中各个脚本名称以及脚本是否启用的信息
for policy in grouppolicy_set:
enabled = "enabled" if policy.enable else "disabled"
policys[policy.name] = {
"status": enabled
}
# settings里面的各小项必须得带上,否则会创建不成功
"settings": {
"name": template.name,
"watchguard_offline_configs": "",
"unixfileanalysis_disable_xdev": "no",
"unixfileanalysis_include_paths": "",
"unixfileanalysis_exclude_paths": "",
"unixfileanalysis_file_extensions": "",
"unixfileanalysis_max_size": "",
"unixfileanalysis_max_cumulative_size": "",
"unixfileanalysis_max_depth": "",
"unix_docker_scan_scope": "host",
"sonicos_offline_configs": "",
"netapp_offline_configs": "",
"junos_offline_configs": "",
"huawei_offline_configs": "",
"procurve_offline_configs": "",
"procurve_config_to_audit": "Saved/(show config)",
"fortios_offline_configs": "",
"fireeye_offline_configs": "",
"extremeos_offline_configs": "",
"dell_f10_offline_configs": "",
"cisco_offline_configs": "",
"cisco_config_to_audit": "Saved/(show config)",
"checkpoint_gaia_offline_configs": "",
"brocade_offline_configs": "",
"bluecoat_proxysg_offline_configs": "",
"arista_offline_configs": "",
"alcatel_timos_offline_configs": "",
"adtran_aos_offline_configs": "",
"patch_audit_over_telnet": "no",
"patch_audit_over_rsh": "no",
"patch_audit_over_rexec": "no",
"snmp_port": "161",
"additional_snmp_port1": "161",
"additional_snmp_port2": "161",
"additional_snmp_port3": "161",
"http_login_method": "POST",
"http_reauth_delay": "",
"http_login_max_redir": "0",
"http_login_invert_auth_regex": "no",
"http_login_auth_regex_on_headers": "no",
"http_login_auth_regex_nocase": "no",
"never_send_win_creds_in_the_clear": "yes" if kwargs["never_send_win_creds_in_the_clear"] else "no",
"dont_use_ntlmv1": "yes" if kwargs["dont_use_ntlmv1"] else "no",
"start_remote_registry": "yes" if kwargs