路由器安全漏洞深度剖析与防护指南

本文深入分析路由器常见安全漏洞,包括硬编码凭证、认证绕过、命令注入、缓冲区溢出等,提供详细的技术架构分析和防护建议,帮助开发者和安全团队构建更安全的网络设备。

路由安全:规避路由器陷阱

引言

Wi-Fi路由器一直是攻击者的诱人目标。一旦被攻陷,攻击者可能获得受害者内部网络或敏感数据的访问权限。此外,攻击者持续将新的路由器漏洞纳入其武器库,用于僵尸网络(如Mirai僵尸网络)的趋势仍在持续。

消费级设备由于存在许多安全缺陷,对攻击者尤其具有吸引力。安全性较低的设备通常包含多个攻击者可轻松利用的漏洞,使其成为易受攻击的目标。另一方面,更安全的设备提供了宝贵的学习经验和见解。

本文从技术角度概述路由器中的漏洞,旨在提高安全团队和开发人员的意识,并提供避免导致此类漏洞的错误的方法。我们还将研究影响不同供应商设备的过往漏洞,从中吸取教训。尽管以下内容主要关注路由器,但所学经验同样适用于其他网络设备。

免责声明:本文未涵盖所有漏洞类别。

攻击面

路由器的攻击面可能比预期的要大。这是因为其上运行着各种服务。每个从外部主机(无论是局域网还是广域网接口)接收请求的服务都构成了攻击面,因为畸形请求可能执行服务中的易受攻击代码路径。下面我们简要探讨路由器上处理外部请求的常见服务。

管理面板

网络设备的管理面板托管着设备所有者/管理员可更改的各种配置。每个输入字段都是一个攻击面,因为它们由设备上运行的Web服务处理。例如,可能存在用于阻止进出特定IP地址流量的输入字段。Web服务处理此请求的方式可能容易受到命令注入攻击。简而言之,管理面板为攻击者提供了巨大的攻击面。

有人可能认为这不是一个非常令人担忧的攻击面,因为攻击者需要首先通过身份验证才能进入管理面板才能访问此攻击面。这是事实,因此许多CVE以“已认证”开头,例如“已认证命令注入”,表明利用该漏洞需要身份验证。然而,攻击者可能会发现身份验证绕过,或者管理面板上可能存在某些不验证用户是否已认证的端点。身份验证绕过可以与“已认证漏洞”链接利用;不需要身份验证的端点上的漏洞被归类为“未认证”,例如“未认证缓冲区溢出”。

其他服务

除了管理面板,路由器通常还运行其他处理各种协议请求的服务,例如FTP、Telnet、动态主机配置协议或通用即插即用。这些服务也构成了路由器上的攻击面。

某些服务(如DHCP或UPnP)不需要身份验证。此外,在某些设备上,某些服务可从WAN接口访问,这意味着不在本地网络上的远程攻击者也可以访问这些服务并利用其上的任何漏洞。对于如此可访问的服务,确保其安全性尤为重要。

不良配置

首先,我们讨论一些路由器上存在的配置错误。我们将讨论的错误都遵循访问主题,即:

  • 通过硬编码凭证访问
  • 从远程网络访问服务
  • 运行服务或普通用户对root权限的访问

硬编码凭证

设备的固件包含设备操作使用的程序和配置文件的存档。相同的固件分发并安装在同一型号的所有设备上。因此,如果设备上运行的任何服务的凭证在固件中硬编码,则每个设备都将使用相同的凭证。

影响

可以访问固件的攻击者(通常可以从供应商网站下载)可以提取文件并检查它们以获取凭证。如果此类服务暴露在互联网上,世界任何地方的任何人都可能能够在设备上获得shell、访问敏感数据或操纵设备的设置,具体取决于哪个服务易受攻击。

在危险性较低的情况下,此类易受攻击的服务可能不会暴露在互联网上,而仅暴露在局域网上。这虽然不那么危险,但与设备在同一网络中的任何人都将能够访问这些暴露的服务。对于路由器而言,连接到其Wi-Fi网络的任何人都可以滥用暴露服务上的硬编码凭证。对于家庭网络中的路由器来说,这 arguably 是可以接受的,因为知道Wi-Fi密码的每个人都是家庭成员或可信赖的朋友。然而,对于咖啡馆或餐厅等公共场所的路由器来说,这是不稳定的。

示例

下面,我们分享一些过去由他人报告的路由器硬编码凭证的示例。

CVE-2022-46637报告称,ProLink PLDT家庭光纤PRS1841U V2路由器包含硬编码的Telnet和FTP凭证。此外,这些服务暴露在互联网上,并且可以通过Shodan找到易受攻击的设备。

CVE-2020-29322详细说明,版本为1.07的D-Link路由器DIR-880L的telnet服务使用硬编码的用户名和密码启动。从公告中,无法确定该服务是否总是在启动时启动,以及该服务是否暴露给WAN/互联网。然而,如果这两点都为真,这将为任何攻击者提供在设备上获取shell的简单后门。

CVE-2019-14919是关于Billion智能能源路由器包含Telnet使用的登录管理二进制文件中的硬编码密码。文章中未提及该服务是仅暴露给WAN还是LAN。除此之外,与硬编码凭证无关,在管理面板中还发现了一个以root权限运行的Web shell。

建议

我们建议在管理员通过管理面板为其设置强密码之前,禁用所有需要身份验证的服务,例如FTP或Telnet。

至于管理面板密码,常见的做法是在工厂为每个设备随机生成密码,并将密码作为贴纸贴在设备底部或包装内的便条上。在首次登录时,将要求管理员在可以使用管理面板之前更改密码。

暴露给互联网的服务

如上所述,攻击者可以使用硬编码凭证访问暴露给互联网的服务,而不仅仅是可从局域网访问的服务。然而,如果没有硬编码凭证,将管理面板或FTP服务器等服务暴露给互联网是否可以接受呢?

路由器上可能运行许多服务,例如FTP、SSH、UPnP、管理面板、VPN。尽管凭证可能不是硬编码的,但这些服务中可能存在不需要身份验证并可能导致设备上RCE的漏洞。因此,除非必要,否则不将所有服务暴露给互联网仍然更安全。

建议

理想情况下,FTP、Telnet或SSH等服务默认不应开启,而应仅在设备管理员通过管理面板请求时开启。如果管理员希望启用服务,应要求他指定这些服务是暴露给互联网,还是仅暴露给局域网。设备不应在管理员不知情或不同意的情况下将这些服务暴露给互联网。更理想的情况是,管理员在将这些服务暴露给互联网之前完全了解此类风险并愿意承担它们。

以root身份运行的服务

在PC上,将普通用户与超级用户分开是很常见的。然而,对于许多消费级路由器来说,情况并非如此。其中许多只有root用户,并且每个服务都以root身份运行。在此类设备上,FTP/Telnet/SSH/管理面板Web服务以root身份运行。因此,当攻击者在服务上获得RCE时,他也获得了具有root权限的shell。当服务被利用时,没有权限分离来防止整个设备受到威胁。

我们在下面列出一些示例:

  • MI AIoT Router AC2350
  • D-Link DIR-890L
  • NETGEAR Nighthawk RAX30

上面的文章显示,无需权限提升漏洞即可获得root shell,这意味着没有权限分离。

建议

每个进程/服务应作为不同的用户运行,应用最小权限原则。例如,httpd服务作为web用户运行;upnpd服务作为upnp用户运行;ftp服务作为ftp用户运行,依此类推。在此配置下,当服务被利用时,攻击者无法在没有权限提升漏洞的情况下危害其他服务或整个设备。

无密码sudo?

在我们检查过的某些路由器上,它们的管理面板Web服务不是以root身份运行,而是以普通用户身份运行。这很好。然而,为了执行一些系统级操作,他们希望使用需要root权限的shell命令。例如,iptables命令。

反面示例

考虑管理面板支持使用iptables阻止进出特定IP地址流量的场景。为了实现这一点,开发人员可能会尝试引入一个SUID二进制文件,其工作方式类似于sudo但不需要密码。换句话说,一个SUID二进制文件,它将命令字符串作为参数,并以root权限将该命令字符串作为shell命令运行。下面显示了一个简单的示例:

1
2
3
4
5
6
int main(int argc, char** argv)
{
  setuid(geteuid());
  system(argv[1]);
  return 0;
}

然后,开发人员可以使用custom_sudo运行iptables命令,如下所示。

1
2
snprintf(cmd, 255, "iptables -A INPUT -s '%s' -j DROP", ip_addr_to_block);
execve("/usr/sbin/custom_sudo", { "/usr/sbin/custom_sudo", cmd, 0 }, 0);

显然,拥有这样的程序违背了拥有不同用户的目的,因为所有用户都可以使用此程序以root身份运行任何命令,违反了最小权限原则。

建议

不建议通过向SUID程序提供整个命令字符串来执行需要root权限的操作。相反,我们建议使用一个SUID程序,该程序仅将必要的值作为参数,然后在执行所需操作时使用它们。

对于上面阻止IP地址的示例,我们建议使用一个名为block_ip_addr的SUID程序,该程序仅将IP地址作为参数,然后在内部使用给定的IP地址字符串执行iptables操作。例如:

1
execve("/usr/sbin/block_ip_addr", { "/usr/sbin/block_ip_addr", ip_addr_to_block, 0 }, 0);

然后,block_ip_addr的实现可以如下:

1
2
char* ip_addr_to_block = argv[1];
execve("/bin/iptables", { "/bin/iptables", "-A", "INPUT", "-s", ip_addr_to_block, "-j", "DROP", 0 }, 0);

如果对所有需要root权限的命令都这样做,则不再需要custom_sudo程序,可以将其完全从系统中删除。

但是,如果开发人员坚持使用这样的custom_sudo程序,请至少验证将要执行的程序在允许的程序列表中。例如:

1
2
3
4
5
if (strncmp(argv[1], "/bin/iptables ", strlen("/bin/iptables "))) {
  // 如果strncmp返回非零值,命令字符串不以预期的`iptables `开头
  // 中止
  ...
}

请注意,在上面的检查中,/bin/iptables 以空格字符结尾。这是为了确保命令字符串确实运行的是/bin/iptables程序,而不是类似iptablesfake的东西。此外,使用绝对路径,而不是程序名称,以防止调用错误的程序,因为程序路径是根据PATH环境变量确定的。例如,如果PATH变量配置为这样,则可能会执行攻击者创建的/home/user/iptables,而不是/bin/iptables

此外,确保没有命令注入漏洞可以被利用来绕过此类检查。这可以通过使用execve而不是system运行所需命令来实现。

简而言之,在考虑需要超级用户权限的操作的实现时,应遵循最小权限原则,以确保所选实现不会向用户提供超出必要的权限。

总结

上述所有错误配置都有直接的解决方案。然而,这些解决方案的实施会产生额外的开发和测试时间。对于消费者来说,希望所有路由器供应商将这些改进视为“必须拥有”,而不仅仅是“最好拥有”。

漏洞类别

在本节中,我们将讨论影响路由器上运行的服务的以下漏洞。

  • 身份验证绕过
  • 命令注入
  • 缓冲区溢出
  • 格式化字符串漏洞

命令注入和缓冲区溢出是路由器中导致RCE的两个主要漏洞类别。格式化字符串漏洞也可能导致RCE,但在近年来非常罕见。

身份验证绕过

如果服务需要身份验证,其攻击面将大大减少。通常,未经认证的客户端只能发送与身份验证相关的请求,或用于查询有关服务或设备的一些信息。因此,身份验证绕过漏洞对攻击者来说很有价值,因为它打开了整个剩余的攻击面。

除此之外,即使没有RCE,非管理员仍然可以执行身份验证绕过来披露和控制管理面板上的敏感设置。对需要身份验证的其他服务(如FTP、SSH或Telnet)的身份验证绕过也可能导致shell访问或敏感信息泄露。因此,不应轻视身份验证绕过。

示例

在以下小节中,我们分享过去报告的路由器身份验证绕过漏洞的示例。

CVE-2021-32030:身份验证逻辑错误

CVE-2021-32030报告了ASUS GT-AC2900路由器上的身份验证绕过。简而言之,该漏洞是由于身份验证流程中的错误引起的,简化如下:

  • 客户端应提供头字段asus_token以验证进入管理面板。
  • asus_token与从*nvram检索的ifttt_token值进行比较。
  • 如果nvram不包含ifttt_token,则返回空字符串,即空字节。
  • 如果asus_token是空字节,则比较成功,客户端成功通过身份验证。

*nvram代表非易失性RAM。许多路由器使用它来存储应在设备重启后保留的配置值。因此称为非易失性,因为内容会保留,这与普通RAM不同,普通RAM的所有内容在设备关闭时将被清除。

建议

在这种情况下,漏洞是由程序员错误引起的,其中意外的边缘情况输入破坏了身份验证逻辑。相关函数应首先确保ifttt_token不是空字符串,然后再将其与客户端提供的asus-token进行比较。为了格外小心,开发人员还可以添加额外检查以确保asus-token不是空字符串,以防将来它可能与从nvram检索的另一个令牌进行比较。

CVE-2020-8864:身份验证逻辑错误

CVE-2020-8864报告了D-Link DIR-882、DIR-878和DIR-867路由器上的身份验证绕过。漏洞利用与上面ASUS路由器中的相同,尽管身份验证逻辑的实现不同,简化如下:

  • 客户端提供LoginPassword进行身份验证。
  • 使用strncmp将客户端提供的密码与正确密码进行比较,如下所示: strncmp(db_password, attacker_provided_password, strlen(attacker_provided_password));
  • 如果攻击者提交带有空密码的登录请求,strncmp的第三个参数将为0,并返回0,表示比较的两个字符串相等,这是正确的,因为两个字符串的前0个字符相同。结果,身份验证成功。

建议

同样,漏洞是由程序员错误引起的。此处的修复是通过传递strlen(db_password)作为第三个参数,而不是strlen(attacker_provided_password)。

CVE-2020-8863:预期的密码值由攻击者控制

CVE-2020-8863报告了D-Link DIR-882、DIR-878和DIR-867路由器上的另一个身份验证绕过。

上面关于CVE-2020-8864的小节通过省略身份验证流程的一些细节进行了简化。在这里,我们详细描述它,以便我们稍后准确描述此身份验证绕过。

这些路由器使用家庭网络管理协议,这是一种基于SOAP的协议,用于客户端到Web服务器的管理面板请求。身份验证过程如下:

  1. 客户端发送请求消息并从服务器获取身份验证挑战。
  2. 服务器使用值Challenge和PublicKey响应请求。
  3. 客户端应将PublicKey与密码组合以创建PrivateKey。然后,使用PrivateKey和Challenge生成挑战响应,该响应将作为LoginPassword提交给服务器。
  4. 服务器将执行相同的计算,如果LoginPassword匹配,则表示客户端知道正确的密码,身份验证成功。

以上描述取自ZDI博客上的这篇文章。请查看它以获取更多详细信息和代码示例。

在Web服务器二进制文件中,发现存在检查登录请求中PrivateLogin字段的代码路径。如下所示:

1
2
3
4
5
6
7
8
// 如果 PrivateLogin != NULL && PrivateLogin == "Username" 那么 Password = Username
if ((PrivateLogin == (char *)0x0) || (iVar1 = strncmp(PrivateLogin,"Username",8), iVar1 != 0)) {
  GetPassword(Password,0x40);  // [1]
}   
else {
  strncpy(Password,Username,0x40);  // [2]
}   
GenPrivateKey(Challenge,Password,Publickey,PrivateKey,0x80);

如果提交的PrivateLogin字段包含“Username”,则提交的Username值用于生成预期的LoginPassword值,而不是通过调用GetPassword使用用户的实际密码。简而言之,客户端可以控制用于生成预期挑战响应的密码,从而绕过身份验证。

不清楚PrivateLogin字段的目的是什么。由于HNAP是一种过时的专有协议,网上没有文档,我们很难确定此字段的原始用途。

作为一个要点,确保在实现身份验证协议时,密码等秘密不应与客户端提交的值混合。

总结

管理面板上的身份验证绕过漏洞源于程序员的错误,因为他们未能考虑可能破坏身份验证逻辑的边缘情况输入,例如空密码。除此之外,协议的实施也可能存在缺陷。应特别关注审查身份验证例程的实现,以捕获由于此类错误导致的意外绕过。

命令注入

命令注入是路由器或其他网络和物联网设备中常见的漏洞。在本节中,我们讨论易受攻击的代码模式、此类漏洞普遍存在的原因、防止它们的指南,以及过去在各种路由器中的一些示例。

根本原因

命令注入是可能的,因为包含未清理用户输入的命令字符串通过C中的system或popen、Python中的os.system、Lua中的os.execute或io.popen作为shell命令执行。例如,

1
2
sprintf(cmd, "ping %s", ip_addr);
system(cmd);
1
os.system(f"ping {ip_addr}")

在上面的示例中,输入IP地址如127.0.0.1; reboot将导致命令字符串ping 127.0.0.1; reboot,这逃脱了预期的ping命令并执行任意命令,如reboot。

使用shell命令的理由

运行shell命令来执行各种系统级操作绝对不是软件开发中的规范。出于性能和兼容性原因,在个人计算机上运行的软件中,很少看到通过运行shell命令来执行系统级操作,例如文件系统或网络操作。然而,在嵌入式设备的世界中,这几乎是普遍存在的。

这种现象背后的原因在某种程度上是可以接受的。在路由器中,性能不是问题,因为上述操作不是很频繁地执行。兼容性也不受影响,因为程序仅在供应商自己的设备上运行。考虑到这些因素,很容易找到实现所需功能的最简单方法,而这种方法在安全性上可能存在缺陷。

例如,查看NETGEAR的pufwUpgrade二进制文件中的以下函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int saveCfuLastFwpath(char *fwPath)
{
    char command [1024];
    memset(command, 0, 0x400);
    snprintf(command, 0x400, "rm %s", "/data/cfu_last_fwpath");
    system(command);
    // 命令注入漏洞
    snprintf(command, 0x400, "echo \"%s\" > %s", fwPath, "/data/cfu_last_fwpath");
    DBG_PRINT(DAT_0001620f, command);
    system(command);
    return 0;
}

有适当的C库函数用于删除文件和写入文件。但开发人员选择创建以rm和echo开头的shell命令字符串,并使用system运行它们。诚然,shell命令更容易记住和使用,无需回头参考C文档。

另一个这样的例子是,当路由器固件开发人员将用户输入的密码插入命令字符串以使用md5sum计算密码哈希时。编写实现相同目标的C代码需要更多时间和精力。

为了补偿这种减少的努力,至少一些设备供应商确实在将输入插入命令字符串并调用system之前对其输入进行了清理。这很好。然而,只需要一个小错误,例如忘记清理输入字段,就会引入命令注入漏洞,从而允许在设备上执行RCE。由于对命令字符串的某些操作,或者由于命令编写方式的某些意外原因,也可能引入漏洞。例如,CVE-2024-1837,我们很快将发布其公告。由我发现:)

根据我有限的经验,我注意到更昂贵的Cisco和ASUS路由器不会通过shell命令执行操作系统级操作来走捷径,而是使用相应的API函数正确实现它们。如果他们要用用户输入执行外部程序,他们只使用安全函数,例如不易受命令注入影响的execve。通过这些努力,他们消除了管理面板上命令注入的最小可能性。

在下一节中,我们分享一些在需要执行外部程序时防止命令注入的建议。

预防

在本小节中,我们分享安全代码设计指南以防止命令注入。首先,如上文小节中反复提到的,并非所有操作都需要通过shell命令执行,因此除非必要,请避免这样做。

避免系统命令

运行使用shell命令的外部程序的决定是潜在命令注入错误的原因。除了仅仅建议开发人员不要编写此类代码外,安全团队可以通过在调用此类函数的地方发出警告来帮助他们避免使用这些函数。

我们在下面列出了应避免在代码库中使用的此类函数。请注意,该列表并非详尽无遗。安全团队应检查标准库或代码库导入的任何第三方库是否支持其他此类函数。

  • C: system, popen
  • Python: os.system, subprocess.Popen/subprocess.call/subprocess.run 带有 shell=True 参数

不要担心这样的规则是否会破坏兼容性,因为它不会。我们在下面提供了安全的替代方案,它可以做shell命令可以做的同样的事情。

使用参数列表运行可执行文件

有一种安全的方法来运行外部脚本或二进制文件,即通过指定程序并提供参数列表,使用C中的execve或Python中的subprocess.Popen/subprocess.run/subprocess.call。例如,

1
execve("/bin/ping", { "/bin/ping", ip_addr, 0 }, 0);
1
subprocess.Popen(["/bin/ping", ip_addr])

这消除了命令上命令注入的可能性。但是,请注意,这并不保证目标可执行文件中是否存在命令注入,例如上面示例中的/bin/ping

请注意,在C中,execve用目标可执行文件替换当前运行的进程。也就是说,如果execve被管理面板服务器调用并带有/bin/ping,则整个服务将消失,被ping替换。这当然不是预期的行为。记得在调用execve之前fork进程。

但是,请注意下面示例中的代码。它违背了目的,因为它再次运行shell命令。

1
2
sprintf(cmd, "ping %s", ip_addr);
execve("/bin/sh", { "/bin/sh", "-c", cmd, 0 }, 0);

自定义execve

在诸如Lua之类的语言中,可能没有像execve这样的库函数来运行特定程序及其参数列表。在这种不幸的情况下,除了使用该语言中可用的system或popen等效函数之外别无选择。在Lua中,那就是os.execute。

为了保护开发人员免于制作容易受到命令注入影响的命令字符串,开发团队可以创建一个类似于execve的函数,该函数接受可执行路径和参数列表,然后使用这些值制作命令字符串,并将其传递给system执行。可执行路径可以与所有参数连接起来,但有两件重要的事情需要注意:

  • 用单引号包裹每个参数。这是为了防止命令替换,因为在shell命令中,单引号内的内容将按原样作为参数传递。使用单引号,任何形式为$(...)...的字符序列都不会被评估。不要用双引号包裹参数。命令替换仍然适用于双引号内的内容。
  • 转义每个参数中的单引号。如果参数包含单引号,它将在其之前关闭开头的单引号,并且其后的任何命令替换有效负载将被评估。确保每个参数中的所有单引号都通过在其前面加上反斜杠字符进行转义,以便它们不会在参数之前关闭开头的单引号。

下面的示例演示了如何在Lua中实现上述操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function custom_execute(executable_path, args)
    -- 转义参数中的单引号
    local escape_single_quotes = function(str)
        return string.gsub(str, "'", "\\'")
    end

    -- 引用并转义每个参数
    local quoted_args = {}
    for _, arg in ipairs(args) do
        table.insert(quoted_args, "'" .. escape_single_quotes(tostring(arg)) .. "'")
    end

    -- 连接可执行路径和引用的参数
    local command = executable_path .. " " .. table.concat(quoted_args, " ")

    -- 使用 os.execute 执行命令
    os.execute(command)
end

-- 示例用法
local echo_path = "/bin/echo"
local args = {"hello", "world", "hey"}
custom_execute(echo_path, args)

上面的custom_execute函数消除了命令注入的可能性,无论参数列表中可能包含任何用户输入。这样的函数在需要执行外部程序时给开发人员带来安心,并减轻他们考虑用户输入所需任何清理的负担。

避免在shell脚本中使用eval

上面的建议适用于接收客户端输入并在系统上另一个程序的执行中使用此输入值的服务。上述保护确保客户端请求的处理程序免受命令注入的影响。然而,它们并不保证被执行的外部程序的安全性。

考虑以下示例,其中/usr/sbin/custom_script是一个shell脚本,它被赋予一个用户输入值作为参数。执行脚本时没有命令注入。但是,正在执行的脚本内部可能存在命令注入。

1
execve("/usr/sbin/custom_script", { "/usr/sbin/custom_script", user_input, 0 }, 0);

考虑以下shell脚本,它将参数插入命令字符串并以各种方式执行它。

  • 使用eval。
  • 使用$(...)
  • 使用...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/sh
cmd="echo $1"

files=`eval "$cmd"`
echo $files

files=$($cmd)
echo $files

files=`$cmd`
echo $files

当使用aaa;whoami作为参数执行时,上面脚本的输出如下。

1
2
3
4
$ ./script 'aaa;whoami'
aaa user        // 发生命令注入
aaa;whoami
aaa;whoami

请注意,当执行命令替换时,没有命令注入。这是因为参数$1作为参数传递给cmd中写入的echo程序。这意味着包含aaa;whoami的整个参数字符串作为单个参数传递给echo。

另一方面,在eval的情况下,$1被插值,即扩展为字符串并插入到命令字符串中,导致echo aaa;whoami成为被执行的命令。在这种情况下存在命令注入,如上面附加的输出所示。

因此,避免在shell脚本中使用eval,以防止由于可能错误处理来自用户输入的参数而导致的任何潜在命令注入漏洞。

可操作步骤

总结以上建议,我们建议开发和

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计