Nessus漏洞插件编写(二)
NASL – Nessus 攻击脚本语言
渗透测试在已识别的机器上运行实际漏洞,并明确是否安全,以免遭受黑客攻击。如果渗透测试失败,则肯定任何内部或外部实体都可以利用您的计算机资源。这些漏洞每天都在飞速增长,因此渗透测试的数量也相应增加。有人必须不断编写这些越来越新的渗透测试。
当今世界上使用最广泛的渗透测试工具是 Nessus,可用于 Linux/Unix 和 Windows 操作系统。它是开源工具成功的典范。Nessus 拥有超过 8000 个渗透测试,每天都有新的测试被编写。所有测试均不是用 C 或 perl 编写的,而是用 NASL 编写的——NASL 是一种专门用于编写这些测试的脚本语言。我们的工作是教您如何编写最复杂的渗透测试。
该软件在 Linux 操作系统上是免费的,而在 Windows 上则需要付费。也许是因为企业界只使用付费软件!
我们首先创建一个目录 C:\nasl,并将路径变量设置为 C:\program files\tenable\newt。这是安装 nasl 解释器 nasl.exe 的位置。然后我们编写第一个 nasl 程序 a.nasl。
display函数
a.nasl
1 |
|
要运行nasl解释器,命令是
/opt/nessus/bin/nasl c:\nasl\a.nasl
这将导致输出如下
NASL 是一种脚本语言,因此它简单易用。与 C/C++ 不同,它对程序员来说需要遵循的规则较少。我们假设您以前使用过某种语言,不必是编程语言,JavaScript 也可以。与每种语言一样,nasl 有自己的一套内置函数。语言的丰富性和易用性在于它提供的函数数量。函数是一个带有开括号和闭括号的单词。
在上面的代码中,display 函数用于在屏幕上显示一些文本。我们可以指定多个字符串,这些字符串是文本,但位于双引号内。\n 表示换行。我们为 display 函数提供了两个参数或值。display 函数的功能与 C 中的 printf 相同。此外,很多 nasl 看起来和感觉起来都像 C。我们看到两次 hi,而不是一次看到它。出于某种原因,display 函数会显示两次内容。
显示(“hi”,“\n”)
解析错误,意外的 $,预期为 ‘;’ 解析错误在第 2 行或附近
每种语言都有表示行尾或句号的规则。有些语言允许使用 Enter 来表示结束,而其他语言(如 C)则使用分号。在 nasl 中,我们必须在函数的闭括号后使用分号。如果没有分号(如前面的示例),则会引发错误。使用分号表示逻辑结束。
b.nasl
1 |
|
输出
i 的值为 20
i 的值为 30
i 的值为 31
i 的值为 Vijay Mukhi
每种语言都允许使用变量。在 NASL 中,无需创建变量,只需使用它即可。因此,我们创建了一个变量 i 并将其设置为 20。显示函数显示其值。请记住,nasl 不会评估双引号中的任何内容,包括变量名。+ 运算符将其值增加 10,然后使用 ++ 将变量的值增加 1。最后,我们做了一件不可思议的事情,我们将变量 i 的值设置为字符串。nasl 根本没有抱怨。因此,在 nasl 中我们不会声明或定义变量,我们只是使用它。变量的数据类型可以在程序中途更改。Nasl 处理所有内部事务。
open_sock网络套接字,连接端口
c.nasl
i = open_sock_tcp(79);
display(“The value of i is “ , i , “\n”);
The value of i is 0
让我们开始用 nasl 做一些真正有用的事情,即编写一个端口扫描器并同时学习 nasl。NASL 有无数的网络函数。函数 open_sock_tcp 在作为参数提供的特定端口上打开套接字或网络连接。由于我们没有指定任何 IP 地址,此函数会检查我们的机器上的端口 79 上是否有服务或服务器在运行。由于我们没有运行这样的服务器,因此结果为 0。
d.nasl
1 |
|
i 的值为 1
http 或 www 服务器监听端口 80,因此使用此端口号会显示 1 作为答案。我们的网络上有两台机器,我们工作的那台机器的 IP 地址为 70.0.0.10,另一台的 IP 地址为 70.0.0.2。我们现在以如下方式运行 nasl
/opt/nessus/bin/nasl -t 192.168.111.2 d.nasl
i 的值为 1
–t 选项用于指定将接收数据包的主机的名称。我们的代码保持不变,但函数现在将检查 IP 地址 70.0.0.2 上的端口号。为了确认此操作,我们在另一个 dos 机器上运行了 snort 开源 IDS。这有助于跟踪 nessus 发送的实际数据包。您可以使用 ettercap 或 ethereal 或任何其他数据包嗅探器或记录器。
1 |
|
如果端口开放,value的值为1,反之,为0
编程中有一句老话:只要能用数字,就用变量代替。因此,我们将变量 i 设置为 80,并将其作为参数传递给函数 open_sock_tcp。既然我们有了代码来告诉我们哪个端口是打开的,我们只需将其复制到所有端口即可。
for循环
e.nasl
for ( i = 10 ; i <= 12 ; i++)
{
display(“Value of i is “, i , “\n”);
}
i 的值为 10
i 的值为 11
i 的值为 12
当我们想要重复代码时,会使用 for 循环。它需要三个以分号分隔的实体。直到第一个分号执行一次,我们将变量 i 的值设置为 10。第二个实体检查条件,如果 i 小于 12,则为真,然后执行 {} 中的代码。在这里我们显示 i 的值。最后执行 for 循环中的最后一个实体,即 ; 和 ) 之间的代码。这里 i 的值增加 1。只要 i 小于 12,for 循环条件就成立。当它变成 13 时,for 循环退出。
if条件语句
f.nasl
1 |
|
80 端口已打开
79 号端口已关闭
if 语句用于在代码中做出决策,从而使语言更加智能。sock 变量可以具有 1 或 0 的值。if 语句接受一个条件,如果为真,则执行括号后面的代码。如果为假,则执行 else 后面的代码(这是可选的)。nasl 中的 True 是 1 或任何正值,0 表示 false。因此,使用此功能,我们可以获得更易读的输出。
g.nasl
1 |
|
现在,我们将代码放在一个 for 循环中,该循环将变量 i 从 20 变为 25,并显示打开和关闭的端口。pot 扫描器以这种方式工作。它向每个端口发送一个 Syn 数据包。如果端口是打开的,它会收到一个 Syn-Ack。如果端口是关闭的,则会收到一个 Rst 数据包。tcp rfc 中有更多关于此的信息。出于某种原因,该函数发送了两次数据包。
1 |
|
端口 7 已打开
9号端口已打开
13号端口已打开
17号端口已打开
19号端口已打开
21 号端口已打开
25 号端口已打开
42 号端口已打开
53 号端口已打开
80 端口已打开
135 号端口已打开
139 号端口已打开
端口 445 已打开
端口 637 已打开
端口号 1002 已打开
上述程序扫描前 1024 个保留端口并显示打开的端口。IP 地址 70.0.0.2 是一个 Windows 2000 机器,没有对默认安装进行任何更改。输出清楚地表明,这个开箱即用的 Windows 2000 操作系统不安全,因为它默认打开了大量端口。当我们将 for 循环改为迭代 65535 次时, 我们惊恐地发现打开的端口数量。
让我们远离端口扫描器,学习更多语言
1 |
|
abc();
In abc
函数
Function abc is user-defined. To write such a function we first use the keyword function, the name abc and then the code within brackets. The only restriction here is that the code must be placed before the function call.
h.nasl
1 |
|
1 |
|
返回函数返回一个值。在本例中,当我们返回 100 时,i 的值就是 100。
i.nasl
1 |
|
在 abc
abc 返回 30
函数调用与 C 语言中的调用方式非常不同。在这些语言中,我们需要记住参数的顺序,但在 NASL 中,我们使用不同的方法。每个参数都有一个名称,我们在参数值前面加上名称。因此,我们不会将 abc 函数调用为 (20,10),而是调用为 (zzz:20, val:10)。参数的顺序并不重要,因此可以将函数 abc 调用为 (val:10, zzz:20)。哪种方法更好由用户决定。
1 |
|
220 mach2 Microsoft ESMTP MAIL 服务,版本:5.0.2195.6713 已于 2005 年 7 月 4 日星期一 09:40:36 +0530准备就绪
nasl -t 70.0.0.2 c:\nasl\a.nasl
在上面的代码中,我们首先打开一个套接字到端口 25,即 SMTP 端口。我们必须再次提醒您,IP 地址为 70.0.0.2 的 Windows 2000 计算机上运行着一个 SMTP 服务器。您可以指定任何运行着 SMTP 服务器的 IP 地址。
SMTP 服务器在收到连接数据包后,会发送一个数字 220 来表示成功,同时发送一个标语,标语上会给出其版本号和名称。为了捕获此标语,我们使用了 recv_line 函数,该函数带有两个参数,分别为套接字和长度。
该函数使用我们刚刚打开的套接字句柄 sock 和要返回的最大行长度 1024 进行调用。变量数据现在将包含横幅。使用显示函数显示该值。
sock = open_sock_tcp(21);
数据 = recv_line(套接字:sock,长度:1024);
显示(数据);
220 mach2 Microsoft FTP 服务(版本 5.0)。
FTP 协议监听 21 端口,规则与 SMTP 相同,即连接到 ftp 服务器后,服务器向客户端发送欢迎横幅。
abc();
c:\nasl\a.nasl(70.0.0.2): 未定义函数‘abc’
由于 abc 函数未定义,因此我们得到一个错误。拥有函数的整个想法就是重用该代码块。但是,将函数放在 nasl 文件中会破坏共享的想法。因此,将所有函数代码放在 inc 文件中是明智的。我们创建 b.inc 作为
文件包含include
b.inc
1 |
|
a.nasl
1 |
|
然后使用 include 函数,将文件引入并添加到我们的代码中。最终结果是 b.inc 中存在的所有代码都添加到我们的 nasl 文件中。如果我们不指定完整路径名,它会在另一个目录中查找该文件。
1 |
|
c:\nasl\a.nasl(70.0.0.2): 未定义函数‘get_http_port’
编译器强烈地指出它不理解 get_http_port 函数。该函数不是内置函数,而是存在于名为 http_func.inc 的文件中。
1 |
|
端口是 80
这个 inc 文件 http_func.inc 是由编写 nessus 的聪明人编写的。它位于目录 c:\program files\tenable\newt\plugins\scripts 中。有超过 37 个这样的 inc 文件。函数 get_http_port 接受一个参数,我们假设它是系统使用的 http 端口。
此函数将检测连接到目标计算机上的 http 服务器时使用的实际 http 端口。我们之所以知道这一点,是因为我们已经研究过组成我们函数的代码。稍后我们将解释该函数的实际工作原理。通常,http 端口为 80。最好使用上述函数找出目标计算机上的 http 端口,而不是假设端口是什么。
http_func.nasl
1 |
|
HTTP/1.1 200 正常
上述程序从目标机器读取整个 html 文件。使用 http 端口号调用 http_open_socket 函数来打开套接字。我们本来可以使用 open_sock_tcp 函数,但选择了上述函数,因为我们在网上找到的程序使用了该函数。两者都执行相同的功能,它返回连接的句柄。
string 函数可以方便地连接各个字符串,我们用它来创建 http 请求。http 请求以 Get 子句开头,然后是斜杠, 后面是文件名。默认情况下,没有文件引用 apache 世界中的 index.html,在 IIS 世界中引用 default.htm。然后使用 send 函数将字符串发送到目标。send 函数需要两个命名参数,socket 参数传递 sock 变量,data 参数传递 req 字符串。然后,不使用在一行处停止的 recv_line 函数,而是使用更通用的 recv 函数,它采用相同的参数、套接字和长度,并在 r 中返回从套接字接收的数据。
显示函数最终用于显示收到的文件内容,并且像好人一样,我们使用 http_close_socket 关闭套接字句柄。 收到的文件以标头开头,然后是实际内容。标头是一个以冒号结尾的单词。
1 |
|
主机上运行的 Apache 服务器
主机上运行的 IIS 服务器
nasl -t 70.0.0.2 c:\nasl\a.nasl
nasl -t 70.0.0.10 c:\nasl\a.nasl
IIS 在主机 70.0.0.10 上运行,Apache 在主机 70.0.0.2 上运行。每个 Web 服务器都会添加一个名为 Server 的标头,后跟其名称,例如在 Apache 中,我们会看到 Server: Apache。要提取服务器名称,只需搜索字符串“Server:”。由于此功能非常常用,NASL 提供了一个运算符 ><。此运算符在 >< 之前指定的字符串中查找之后指定的字符串。我们 在字符串 r 中搜索 Server: Apache。如果找到匹配项,则显示一条消息。同样,IIS 使用名称 Microsoft-IIS。此检查很重要,因为某些漏洞仅在具有特定版本号的特定服务器上起作用。因此,在运行漏洞之前,必须进行这些先决条件检查,以确保系统符合我们的条件。
现在我们来探讨几个实际漏洞,并向您展示它们的编码方式。我们将使用 nessus 的人员提供的示例代码,因为本教程的整个想法是理解 nasl 代码。
FTP匿名登陆漏洞
FTP 漏洞
1 |
|
220 mach2 Microsoft FTP 服务(版本 5.0)。
331 允许匿名访问,发送身份(电子邮件名称)作为密码。
230 匿名用户登录。
与往常一样,打开与端口 21 的套接字连接,并响应该连接接收初始横幅。FTP rfc 详细说明了 ftp 的规则,根据该规则必须发送用户名。为此,使用命令 USER,然后是用户名 anonymous。对此命令的响应是一条声明,澄清允许匿名用户访问服务器,并且需要电子邮件 ID 作为密码。要提供此信息,使用 PASS 命令和电子邮件 ID。允许使用任何密码。完成后,我们便登录了。
1 |
|
我 = 1
我们不需要写下所有代码,而是可以使用函数 ftp_log_in,它接受一个套接字、一个用户并传递参数并执行内部管道。
sock = open_sock_tcp(21);
i = ftp_log_in(套接字:sock,用户:“anonymous1”,密码:“vijay@mukhi.com”);
显示(“i = ”,i,“\n”);
我 = 0
anonymous 用户比较特殊,因为他是唯一一个无需有效密码即可登录的用户。将用户名更改为 anonymous1 后,结果将为 0,因为访问被拒绝。
1 |
|
220 mach2 Microsoft FTP 服务(版本 5.0)。
331 anonymous1 需要密码。
530 用户 anonymous1 无法登录。
如果用户名是 anonymous1,则应提供有效密码。但是,主机 70.0.0.2 上不存在用户 anonymous1,因此会产生错误。
让我们编写一个简单的 nasl 脚本,告诉我们是否允许匿名 ftp 访问。这是所有 ftp 服务器都需要进行的一项检查,以确保不允许匿名登录。
sock = open_sock_tcp(21);
i = ftp_log_in(套接字:sock,用户:“匿名”,密码:“vijay@mukhi.com”);
如果 ( i == 1 )
display(“允许匿名 ftp 访问。这是一件坏事\n”);
别的
display(“不允许匿名 ftp 访问。这是一件好事\n”);
允许匿名 ftp 访问。不好!!!
sock = open_sock_tcp(21);
如果(ftp_log_in(套接字:sock,用户:“管理员”,密码:“”))
display(“没有管理员密码\n”);
别的
display(“管理员密码有效\n”);
管理员拥有有效密码
许多系统都有一个名为 Administrator 的用户,该用户没有密码。上述脚本尝试使用空白密码以管理员身份登录。当 if 语句为真时,会显示错误消息。
包括(“ftp_func.inc”);
sock1 = open_sock_tcp(21);
ftp_log_in(套接字:socket1,用户:“anonymous”,密码:“aa@bb.com”);
数据=字符串(“MKD /vijay \ r \ n”);
发送(套接字:socket1,数据:数据);
r = recv_line(套接字:socket1,长度:1024);
显示(“sock1:”,r,“\n”);
sock1: 257“/vijay”目录已创建。
我们再次匿名登录,然后使用 MKD 命令在运行 ftp 服务器的主机 70.0.0.2 上创建一个目录。Microsoft 的 ftp 服务器根目录是 c;\inetpub\ftproot,因此将在其中创建目录 vijay。recv_line 函数声明上述命令是否已成功执行。
包括(“ftp_func.inc”);
sock1 = open_sock_tcp(21);
ftp_log_in(套接字:socket1,用户:“anonymous”,密码:“aa@bb.com”);
发送(套接字:socket1,数据:“MKD /vijay2\r\n”);
r = recv_line(套接字:socket1,长度:1024);
显示(“sock1:”,r,“\n”);
上述程序失败,因为所有 ftp、smtp、http 命令都以 \r\n 结尾。只有双引号或字符串函数才能将 \r\n 视为回车符。因此,只要需要 \r\n,就可以安全地使用字符串函数或单引号。显示命令首先调用字符串函数,然后显示文本。在上述情况下,系统等待我们输入回车符。
发送(套接字:socket1,数据:’MKD /vijay2\r\n’);
正确的方法是使用单引号。
包括(“ftp_func.inc”);
sock1 = open_sock_tcp(21);
ftp_log_in(套接字:socket1,用户:“anonymous”,密码:“aa@bb.com”);
数据 = 字符串(“CWD /\r\n”);
发送(套接字:socket1,数据:数据);
r = recv_line(套接字:socket1,长度:1024);
显示(“sock1:”,r,“\n”);
pasv = ftp_get_pasv_port (套接字:socket1);
display(“第二个端口是 “, pasv , “\n”);
sock2 = open_sock_tcp(pasv);
数据 = 字符串(“RETR a.txt\r\n”);
发送(套接字:socket1,数据:数据);
r1 = ftp_recv_line(套接字:socket2);
显示(“文件内容:”,r1,“\n”);
r = ftp_recv_line(套接字:socket1);
显示(“sock1:”, r,“\n”);
sock1:250 CWD 命令成功。
第二个端口是 1677
文件内容:vijay mukhi
sock1:125 数据连接已打开;传输开始。
现在我们继续编写一个程序,该程序将从服务器检索整个文件。第四行实际上不是必需的,它只是向我们展示了可以在 ftp 世界中使用的另一个命令,CWD 用于将当前工作目录更改为另一个。此处目录更改为 / 或默认的根目录。recv_line 函数显示此命令是否成功。
ftp 协议在传输数据时使用 2 个端口。端口 21 用于传递命令,如 MKD、CWD 等,而文件传输则使用另一个端口号,称为数据端口。函数 ftp_get_pasv_port 用于从 ftp 服务器协商第二个端口地址。我们获得一个大于 1024 的端口号,在我们的例子中是 1677。请记住,这个端口号将接收文件的内容,但它会随着每个文件而变化。因此,我们创建了两个通道,一个用于发送命令,另一个用于发送实际数据,即数据通道。完成后,将在此端口 1677 上打开套接字连接,并将字符串 RETR 与文件名一起使用并发送到第一个端口或控制通道。发送函数发送字符串后,我们将等待 sock2 接收文件的内容。同时,sock1 会让我们了解文件传输的情况。这可能会有点令人困惑,因为文件是使用 sock2 传输的,而 sock1 用于控制传输。
我们希望您在 ftproot 目录中创建了一个名为 a.txt 的文件。函数 ftp_get_pasv_port 只是将命令 PASV 发送到服务器。服务器发送字符串 Entering Passive mode 并在圆括号中以点分十进制格式包含其 IP 地址,但以逗号分隔而不是以点分隔。后面跟着 2 个数字,第一个数字乘以 256,第二个数字乘以 1,然后相加得到数据通道的端口号。传输结束后,服务器发送一条消息“226 Transfer complete\r\n”。一些 Linux 守护程序允许用户以用户 NULL 和密码 NULL 身份登录。也可以执行此检查。最后,要正常关闭 ftp 连接,请使用 Quit 命令。
我们必须承认这是我们使用nasl编写的最简单的ftp客户端。
有一种称为 ftp glob 溢出的攻击,它通过创建过多的目录并列出它们而导致服务器崩溃。
包括(“ftp_func.inc”);
soc = open_sock_tcp(21);
ftp_log_in(套接字:soc,用户:“匿名”,密码:“aa@bb.com”);
端口2 = ftp_get_pasv_port(套接字:soc);
soc2 = open_sock_tcp(端口2);
发送(套接字:soc,数据:’NLST *\r\n’);
b = ftp_recv_line(套接字:soc);
显示(b);
b = recv(套接字:soc2,长度:1024);
显示(b);
125 数据连接已打开;传输开始。
文本文件
文本文件
另一个显示目录内容的程序。数据通道端口号在 port2 中,因此首先打开此通道。然后发送带有通配符的 NLST 命令,该命令列出目录中的文件。目前,有两个文件 a.txt 和 b.txt,因此 * 将列出两个 rgwaw 文件。如果使用的命令是 NLIST a*,则只会列出以 a 开头的文件。数据通道列出文件,soc 通道让我们了解传输的最新情况。然后 RMD 删除目录。
包括(“ftp_func.inc”);
soc = open_sock_tcp(21);
ftp_log_in(套接字:soc,用户:“匿名”,密码:“aa@bb.com”);
端口2 = ftp_get_pasv_port(套接字:soc);
soc2 = open_sock_tcp(端口2);
发送(套接字:soc,数据:’NLST *\r\n’);
b = recv(套接字:soc2,长度:1024);
显示(b);
如果(’.avi’><b)
display(“找到声音文件\n”);
文本文件
病毒
找到声音文件
许多人都担心他们的服务器上是否存在某些声音文件、视频文件。可以使用 NLST 命令循环遍历所有文件,然后检查 ftp 服务器上是否存在具有某些扩展名的某些文件。然而,这变得复杂得多。还需要检查以跟踪虚假 ftp 服务器的横幅。KIBUV.B 蠕虫在端口 7955 上安装了一个虚假 ftp 服务器。它有一个横幅 220 fuckFtpd 0wns j0,也在端口 14920 和 42260 上发现。
某些登录组合也需要仔细检查,例如用户名是 admin,密码是 password。针对不同的用户名和密码组合,有大量的测试。此外,较早版本的 sql server 可以为用户 sa 设置空白密码。此外,通过向 novell ftp 服务器发送空值,可能会使它崩溃。
soc = open_sock_tcp(21);
要求 = 垃圾(15);
ftp_log_in(套接字:soc,用户:req,密码:“aa@bb.com”);
向 ftp 服务器发送大量数据或使用过长的用户名也可能导致服务器崩溃。crap 函数会为我们创建这么大的字符串。在本例中,我们得到了 15 个 X。缓冲区溢出漏洞利用 crap 函数来定位缓冲区溢出的位置。
在上述情况下,我们发送命令 USER XXXXXXXXXXXXXXX,总共 15 个 X。有时,缓冲区可能会因 100 k 的数据而溢出。这就是 crap 函数真正有用的地方。
有一个测试,其中每个 FTP 命令都使用大量数据以及垃圾函数并发送。
soc = open_sock_tcp(21);
req = crap(数据:“ab”,长度:5);
ftp_log_in(套接字:soc,用户:req,密码:“aa@bb.com”);
crap 函数有两个参数,一个是包含要复制的字符串的数据变量,另一个是长度变量中字符串的总长度。由于长度为 5,因此发送的用户名将是 ababa。
soc = open_sock_tcp(21);
ftp_log_in(套接字:soc,用户:“匿名”,密码:“aa@bb.com”);
对于(i=0;i<40000;i=i+1)
{
端口2 = ftp_get_pasv_port(套接字:soc);
soc1 = open_sock_tcp(端口2);
display(“端口号为 “,port2, “\n”);
}
在上面的程序中,我们创建了 40000 个数据通道。这是一种广泛用于创建拒绝服务的方法,其中资源被不必要地使用。根据我们的理解,我们认为系统会为我们创建 40,000 个数据通道。但我们错了,因为一段时间后,ftp 服务器一直给我们相同的端口号。该软件知道有人试图进行 DOS 攻击,并允许其他合法用户访问。它不断将端口号发送到 5000,然后从 1027 重新启动。
显示(“hi \ n”);
退出(0);
显示(“再见”);
你好
exit 函数负责退出。任何时候要退出我们的代码,都会调用 exit 函数。通常会检查产品某个版本的标语。如果版本匹配,我们会立即退出。EXIT 命令用于正常退出 ftp 连接。许多漏洞会发送带有非常长参数的命令。发生缓冲区溢出,服务器关闭。
soc = open_sock_tcp(21);
ftp_log_in(套接字:soc,用户:“匿名”,密码:“nessus@”);
发送(套接字:soc,数据:字符串(“CWD”,rand(),“-”,rand(),“\r\n”));
r = recv(套接字:soc,长度:1024);
显示(r);
550 8482-15703:系统找不到指定的文件。
550 9808-21412:系统找不到指定的文件。
过去,当要求更改到未知目录时,ftp 服务器会泄露根目录的完整路径名。这不是漏洞,而是信息泄露。如今,大多数 ftp 服务器都已修补了这些漏洞。
rand 函数返回一个随机数。该程序执行两次时会给出不同的数字。减号用于分隔两个随机数。但是,我们的 ftp 服务器仍然不会泄露任何目录名称。
IIS 漏洞
1 |
|
获取/NULL.打印机 HTTP/1.1
连接:关闭
主办方:MACH2
指令:无缓存
用户代理:Mozilla/4.75 [en](X11、U;Nessus)
接受:image/gif、image/x-xbitmap、image/jpeg、image/pjpeg、image/png、*/*
接受语言: en
接受字符集:iso-8859-1,*,utf-8
我们通过包含文件 http_func.inc 来启动带有一些 http 辅助函数的脚本。http_get 函数有两个参数,第一个是文件的名称,第二个是端口。然后它创建要发送的实际请求。这避免了对 http 服务器所需的标头进行必要的破解。我们的文件名是 /NULL.printer。该函数会解密其他标头,其中一些是可选的,但既然我们不必编写它们,那何必费心呢。
1 |
|
HTTP/1.1 500 13
服务器:Microsoft-IIS/5.0
日期:2005 年 7 月 5 日星期二 04:57:57 GMT
X-Powered-By:ASP.NET
连接:关闭
内容类型:text/html
网络打印机安装错误。
发现 IIS 错误
Internet 信息服务器 IIS 从版本 5 开始支持 Internet 打印协议 IPP。此协议作为 ISAPI 扩展实现。此扩展中发现缓冲区溢出,因此建议在不使用时禁用此功能。要禁用它,请在 Internet 服务管理器中选择 Web 服务器,右键单击,选择“属性”,选择“主目录”选项卡,选择“配置”。在扩展列中选择 .printer,然后删除。
首先发出请求,然后使用函数 http_recv 从 http 服务器接收数据。如果其中包含字符串 Error in web Printer Install,则我们就知道 IIS 已安装 IPP 协议。这可能会导致安全漏洞。
包括(“http_func.inc”);
包括(“http_keepalive.inc”);
req = http_get(项目:“/b.html”,端口:80);
res = http_keepalive_send_recv (数据:req,端口:80);
显示(分辨率);
HTTP/1.1 200 正常
服务器:Microsoft-IIS/5.0
日期:2005 年 7 月 5 日星期二 06:31:13 GMT
内容类型:text/html
接受范围:字节
最后修改时间:2005 年 4 月 3 日星期日 01:11:30 GMT
ETag:“08d2d11ea37c51:8f6”
内容长度:6
你好 1
使用包含文件,我们可以创建自己的函数,从而缩短必须编写的代码。上面的例子证明了这一点。http_get 函数创建要发送的请求,http_keepalive_send_recv 函数发送请求并等待答复。这两个函数就是我们在服务器上获取文件所需的全部功能。
1 |
|
服务器漏洞无法工作
以前,允许向 IIS 发送类似 GET ../../ 的请求,这最终会导致 IIS 崩溃。上述请求只是告诉 IIS 转到上一级目录,然后再转到上一级。这会导致拒绝服务,因为 IIS 会超出其目录结构。今天 IIS 只是回复说这是一个错误请求。我们发送了一个类似的请求,然后使用睡眠功能睡眠 2 秒。然后我们再次连接到 IIS。由于我们收到了有效的套接字,这仅表示服务器仍在运行。
让我们暂时停止编写 nasl 代码,而是编写一个实际的渗透测试。Nessus 安装在目录 c:\program files\tenable\newt 中。所有 exe 文件都存储在此目录中。其中有一个目录 plugins,该目录有一个包含所有 nasl 脚本的脚本子目录。这里有 8000 多个文件。我们将它们全部移动到另一个目录。因此,从某种意义上说,我们删除了所有 nasl 文件。您没听错,删除了所有 nasl 文件。现在创建一个文件 v.nasl,如下所示。
1 |
|
我们现在运行 newt 目录中的 exe 文件 build.exe。此程序进入 plugins\scripts 目录并读取所有可用的 nasl 脚本。然后,它会构建一个文件 plugins.xml,其中包含已安装插件的详细信息。在我们的例子中,我们有 3 个插件,其中一个是我们的,两个是现有的。如果有 8000 个插件,则构建将需要很长时间才能创建此文件。
现在启动 nessus 或 newt 安全扫描程序,它是您桌面上的一个图标。我们选择第一个选项新扫描任务并指定我们的 C 类网络的 IP 地址 70.0.0.2。然后单击下一步。窗口有四个单选按钮可供选择,我们从中选择最后一个,因为我们想指定我们自己的插件。Newt 运行它能接触到的所有渗透测试。单击下一步会出现一个有两列的屏幕。第一列名为系列,有三个条目。第三个条目显示这是脚本系列。
回到 v.nasl,只有当脚本通过 newt 安全扫描器执行时,description 变量才为真。因此,通过 nasl 运行它不会激活上述代码,因为 description 变量将为假。函数 script_family 有一个参数 english,它指定一个系列名称。渗透测试就是以这种方式分组在一起的。 我们单击它的那一刻,复选框就会被选中,并且属于该系列的所有插件都会显示在右侧。我们只看到一个插件,它带有我们为函数 script_name 提供的参数和参数 english。通过使用语言名称作为参数,可以使用相同的函数来注册不同的语言。 我们使用链接选择它。这将出现一个对话框,您可以在其中提供有关渗透测试的其他有用信息。第一个是使用 script_id 函数指定的脚本的 id。不要给出太大的数字,否则会出错。这仅用于文档。然后我们有插件名称,它是 script_name 函数的重复。接下来是插件文件 v.nasl 的名称。
最后,我们得到了使用函数 script_summary 指定的摘要。然后,我们得到了一个长文本区域,用于描述,使用 desc 数组并以 english 作为参数。我们可以调用变量 desc1,但是我们看到的所有 nasl 代码都调用数组 desc。nasl 中的数组与任何编程语言中的数组类似。此 desc 由 script_description 函数指定。出于某种原因,我们首先需要创建一个数组变量并将此变量传递给描述函数。所有其他函数都直接传递字符串。这里我们采用一种迂回的方式。
然后点击扫描,如果 出现错误,则重新开始。我们将获得一份报告,其中列出了开始时间和结束时间以及扫描主机的 IP 地址以及警告、错误、漏洞等的数量。我们只收到一条警告和以下输出。
主机2-ns (81/tcp)
Vijay Mukhi 安全警告
BID : 420
插件 ID : 11420
我们拥有的唯一一行代码是函数 security warning。它需要一个端口号和一个描述。端口号 81 显示在第一列,描述或数据显示在第二列。函数script_bugtraq_id 被提供了一个数字 420,它是 bugtraq 数据库 id,后面跟着我们的插件 id。bugtraq id 仅供参考。函数 script_category 很重要,因为它指定了脚本的类型。在我们的例子中,我们说我们只是在收集信息。大约有六个这样的类别。
security_hole(端口:81,数据:“Vijay Mukhi 安全警告”);
退出(0);
如果我们将倒数第二行更改为 security_hole,图标将变为 X,并且漏洞数量将增加 1,但警告不会增加。我们不需要重新运行构建程序,因为更改是在代码中进行的,而不是在脚本名称等方面。
security_note(端口:81,数据:“Vijay Mukhi 安全警告”);
最后,将功能更改为安全注释允许我们再次添加注释,我们也看到了不同的图标。还有一种方法可以运行扩展名为 nes 的 exe 文件。这就是我们如何结合我们对 nasl 的知识并创建自己的插件。
创建我们自己的数据包
1 |
|
70.0.0.10
现在让我们创建自己的数据包。可以使用函数 forge_ip_packet 创建 ip 数据包。此函数占用大量参数。我们按照它们在 IP 报头中出现的顺序开始。前四位是使用的 ip 版本,即 4,我们将此值设置为 ip_v 参数。接下来的四位是 ip 报头的长度,在我们的例子中,由于我们没有向 ip 添加任何内容,因此长度为 5。此数字要乘以 4,这样我们得到 20。ip 报头的长度可以从最小 20 到最大 60 不等,因为四位保存从 0 到 15 的数字。参数名称为 ip_hl。 然后我们有服务类型,它表示数据包对路由器的重要性。不幸的是,大多数路由器都忽略了这个称为 ip_tos 的字段。
然后有两个字节表示数据包的总长度。由于我们的数据包只是一个 IP 数据包,因此字段 ip_len 设置为 20。ip_id 字段保存数据包的 ID。大多数情况下,此字段会被忽略。我们的数据包的 ID 为 12。
IP 数据包的最大长度为 65536,但以太网不能承载大于 1500 字节的数据包。因此,如果数据包大小为 3000 字节,则必须将其作为两个数据包或片段发送。字段 ip_off 表示碎片。0 表示无碎片。然后是字段 ip_ttl 或生存时间,它决定了数据包在被杀死或丢弃之前可以穿过的路由器数量。我们选择最大值 255,但即使 20 也太大了。
ip 的工作只是将数据包传送到目的地。IP 从不单独传播,后面还会跟随着其他协议,如 TCP、ICMP 或 UDP。字段 ip_p 是跟在 ip 后面的协议,我们为其指定了一些不存在的协议 2。然后我们有 2 个字节的 ip 校验和,它由内部函数计算得出。接下来的四个字节是源 ip 地址,我们在其中写入 70.0.0.4,即使我们的 ip 地址是 70.0.0.10。字段 ip_src 采用点分十进制表示法中的 ip 地址。最后四个字节是目标 ip 地址。字段 ip_ttl 是可选字段。如果我们给出的值不是 –t 选项指定的 ip 地址,系统将发出错误。这意味着我们可以伪造 ip 数据 包的所有字节,但需要保持 –t 和 ip_ttl 字段保持不变。
函数 this_host 返回我们的 ip 地址。此后,我们调用函数 send_packet 并使用 ip 数据包发送数据包。稍后将解释 pcap_active 参数。
在发送数据包之前,即运行程序,运行 snort,如 snort –dev,然后捕获以下数据包。
Snort 输出
07/06-10:06:34.097968 0:0:E8:DF:A4:66 -> 0:0:E8:D7:5E:7C 类型:0x800 长度:0x22
70.0.0.4 -> 70.0.0.2 PROTO002 TTL:255 TOS:0x0 ID:12 IpLen:20 DgmLen:20
第一行给出了数据包捕获发生的日期和时间以及源和目标的以太网地址。然后显示源和目标的 IP 地址。接下来是协议、ttl、tos、id、ip 长度和数据报的总长度。
ip = forge_ip_packet(ip_hl: 5, ip_v: 4, ip_tos: 1, ip_len: 40, ip_id: 12, ip_off: 0,
ip_ttl: 5,ip_p: IPPROTO_TCP,ip_src: this_host());
tcp = forge_tcp_packet (ip:ip, th_sport:70, th_dport:71, th_seq:4,
th_ack:5,th_off:5,th_flags:TH_ACK,th_win:0x1000,th_x2:0,th_urp:0);
发送数据包(tcp,pcap_active:FALSE);
现在让我们发送一个 ip-tcp 数据包。我们首先使用 forge_ip_packet 函数,其中对于协议我们不使用 2,而是使用 tcp 的值,即 IPPROTO_TCP。如果我们出于某种原因(例如使用常量)不使用 2,则使用代表 tcp 的数字 6。同样,1 代表 icmp 协议。数据包的长度为 40,ip 为 20,tcp 为 20。函数 forge_tcp_packet 接受一个参数 ip,即刚刚创建的 ip 数据包。然后,我们使用参数 th_sport 和 th_dport 从源端口 70 和目标端口 71 开始。这些字段有两个字节大。接下来是 2 个四字节字段,序列号或 th_seq 被赋予值 4,th_ack 是确认号,其值为 5。然后我们有 6 个标志,其中我们只设置一个 TH_ACK 或 ack 标志。 th_off 字段是 tcp 标头的长度乘以 5,与 ip 相同。然后我们有窗口大小或 th_win,它是 无需等待确认即可发送的数据量。最后一个字段是 th_urp 字段或紧急指针。此字段始终为零,因为它是一种告诉对方的方式,数据包中有一些紧急数据请阅读。校验和照常是可选的,th_x2 字段是始终为零的 6 个未使用字节。我们对 tcp 数据包使用相同的 send_packet 函数,而不是 ip。
Snort 输出
07/06-11:47:43.474595 0:0:E8:DF:A4:66 -> 0:0:E8:D7:5E:7C 类型:0x800 长度:0x36
70.0.0.10:70 -> 70.0.0.2:71 TCP TTL:5 TOS:0x1 ID:12 IpLen:20 DgmLen:40
A* 序列:0x4 确认:0x5 胜利:0x1000 TcpLen:20
snort 的 tcp 部分显示 Ack 标志、序列和 ack 号码、窗口大小和 tcp 长度。
07/06-12:17:42.909187 0:0:E8:DF:A4:66 -> 0:0:E8:D7:5E:7C 类型:0x800 长度:0x36
70.0.0.10:1080 -> 70.0.0.2:80 TCP TTL:5 TOS:0x1 ID:12 IpLen:20 DgmLen:40
*****S 序列:0x4 确认:0x5 胜利:0x1000 TcpLen:20
07/06-12:17:42.909705 0:0:E8:D7:5E:7C -> 0:0:E8:DF:A4:66 类型:0x800 长度:0x3C
70.0.0.2:80 -> 70.0.0.10:1080 TCP TTL:128 TOS:0x0 ID:58331 IpLen:20 DgmLen:44 DF
AS 序列:0xBDD4A946 确认:0x5 胜利:0x40E8 TcpLen:24
TCP 选项 (1) => MSS: 1460
07/06-12:17:42.909743 0:0:E8:DF:A4:66 -> 0:0:E8:D7:5E:7C 类型:0x800 长度:0x36
70.0.0.10:1080 -> 70.0.0.2:80 TCP TTL:128 TOS:0x0 ID:8881 IpLen:20 DgmLen:40
***R 序列:0x5 确认:0x5 胜利:0x0 TcpLen:20
ip = forge_ip_packet(ip_hl: 5, ip_v: 4, ip_tos: 1, ip_len: 40, ip_id: 12, ip_off: 0,
ip_ttl: 5, ip_p: 6, ip_src: this_host());
tcp = forge_tcp_packet(ip:ip,th_sport:1080,th_dport:80,th_seq:4,
th_ack:5,th_off:5,th_flags:TH_SYN,th_win:0x1000,th_x2:0,th_urp:0);
发送数据包(tcp,pcap_active:FALSE);
让我们通过向 IP 地址 70.0.0.2 发送一个 syn 数据包来测试我们的代码,该数据包将回复一个 syn ack 数据包。我们使用枚举 TH_SYN 设置 syn 标志。这是 snort 输出中的第一个数据包。机器 70.0.0.2 向我们发送了一个数据包,其中设置了两个标志 SYN 和 ACK。 我们发送了一个序列号 4,返回的数据包将其增加 1 到 5 作为 ACK 号。来自 70.0.0.2 的序列号是一个大数字 0xBDD4A946。当我们的 Windows 副本收到这样的数据包时,它会发回一个 RST 数据包。对于 rst 数据包,序列号和 ack 号没有意义。因此,我们成功发送了一个 syn 数据包,收到了一个 syn ack,Windows 对此表示反对并发回了一个 rst。所有这一切发生都是因为我们正在连接到目标机器端口 80 上运行的 Web 服务器。这是目标端口字段的值,即另一侧的端口。
ip = forge_ip_packet(ip_hl: 5, ip_v: 4, ip_tos: 1, ip_len: 40, ip_id: 12, ip_off: 0,
ip_ttl: 5, ip_p: 6, ip_src: this_host());
tcp = forge_tcp_packet(ip:ip,th_sport:80,th_dport:1080,th_seq:4,
th_ack:5,th_off:5,th_flags:TH_SYN,th_win:0x1000,th_x2:0,th_urp:0);
发送数据包(tcp,pcap_active:FALSE);
07/06-13:33:37.354257 0:0:E8:DF:A4:66 -> 0:0:E8:D7:5E:7C 类型:0x800 长度:0x36
70.0.0.10:80 -> 70.0.0.2:1080 TCP TTL:5 TOS:0x1 ID:12 IpLen:20 DgmLen:40
*****S 序列:0x4 确认:0x5 胜利:0x1000 TcpLen:20
07/06-13:33:37.354678 0:0:E8:D7:5E:7C -> 0:0:E8:DF:A4:66 类型:0x800 长度:0x3C
70.0.0.2:1080 -> 70.0.0.10:80 TCP TTL:128 TOS:0x0 ID:58567 IpLen:20 DgmLen:40
AR 序列:0x0 确认:0x5 胜利:0x0 TcpLen:20
我们现在向端口 1080 发送一个 syn 数据包。我们知道这台机器上的该端口没有运行任何服务器。根据 rfc,如果我们在没有服务器接受数据包的端口上发送 syn 数据包,操作系统必须发送一个 RST 数据包。这正是发生的事情。这就是端口扫描器的工作原理,它不断发送 syn 数据包,如果它收到一个 Ack,则端口是开放的,rst 没有端口开放,没有数据包,我们和目标之间有一道防火墙。
ip = forge_ip_packet(ip_hl: 5,ip_v: 4, ip_tos: 0,ip_len: 100,ip_id:4,ip_off: 0,
ip_ttl:0xff,ip_p:0x03,ip_src:this_host());
ipo = insert_ip_options (ip:ip,代码:0xE4,长度:2,值:raw_string(0x03,0x04));
ipo += 字符串(“ABCDEFG”);
发送数据包(ipo,pcap_active:FALSE);
00 00 e8 d7 5e 7c 00 00 e8 df a4 66 08 00
46 00 00 1f 00 04 00 00 ff 03 47 c5 46 00 00 0a 46 00 00 20
e4 02 03 04
41 42 43 44 45 46 47
我们可以更改有关 ip 报头的所有内容,无论它是否合乎逻辑。现在,我们已经使用了另一个网络嗅探器 ethereal 来向您显示 nessus 发送的字节。安装 ethereal 后,我们选择菜单“捕获”、“选项”,选择网卡,然后选择“捕获”。然后,我们再次单击“捕获”、“开始”,然后在捕获数据包后单击“停止”。默认情况下,我们会得到 3 个窗口,第一个窗口包含所有数据包,第二个窗口包含一个数据包的更高级别视图,第三个窗口包含单个字节。如果我们在第二个窗口中单击某个英文报头名称,我们将在第三个窗口中获得实际字节。第一行是以太网报头。包含 IP 数据包的第二行以 46 开头,而不是 45。这是因为我们的报头现在是 24 字节大,而不是 20 字节。我们将数据包的长度设置为 100 字节,但系统覆盖了我们,而是将其设置为 31 字节或 1f。ip 数据包以源地址和目标地址结尾,然后开始我们的 IP 选项。请记住,IP 报头最多可以有 60 个字节。通常这额外的 40 个字节用于让路由器放置其 IP 地址以充当数据包使用的路径。函数 insert_ip_options 首先获取刚刚创建的 ip 数据包,即 rfc 中指定的 ip 选项的代码,这是一个字节值,我们使用非标准值 e4。然后我们得到 2 个字节后的数据长度和实际字节数。每当我们希望数据包包含特定字节时,总是使用函数 raw_string。这给了我们一个新的数据包 ipo,其大小现在为 24 字节。然后我们向这个 ip 数据包添加一个字符串,该字符串会连接到末尾。这会为我们的 ip 数据包添加另外 7 个字节,使其长度为 31。这就是我们可以创建自己的数据包来模拟缓冲区溢出的方式。
1 |
|
00 00 e8 d7 5e 7c 00 00 e8 df a4 66 08 00
45 00 00 1c 00 03 00 00 40 11 ee c2 46 00 00 0a 46 00 00 02
04 01 04 02 00 08 6b cf
00 00 e8 df a4 66 00 00 e8 d7 5e 7c 08 00
45 00 00 38 0e 94 00 00 80 01 a0 25 46 00 00 02 46 00 00 0a
03 03 89 22 00 00 00 00 45 00 00 1c 00 03 00 00 40 11 ee c2 46 00 00 0a 46 00 00 02 04 0104 02 00 08 6b cf