# PfSense命令注入漏洞分析

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2015-09-09T06:00:00Z
Canonical: https://wepostx.com/topics/1025

> 乌云历史资料归档
>
> **原始作者：** mssp299
> **原始编号：** superkieran-wooyundrops:751
> **原始发布时间：** 2015-09-09 14:00
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

翻译：mssp299
原文地址：[https://www.proteansec.com/linux/pfsense-vulnerabilities-part-2-command-injection/](https://www.proteansec.com/linux/pfsense-vulnerabilities-part-2-command-injection/)

# 0x00 导言

在本文中，我们将向大家介绍在PfSense的2.1.3以及更低版本中的CVE-2014-4688漏洞；对于更高的版本来说，pfSense已经修复了这个漏洞。

# 0x01 Diag_dns.php脚本中的命令注入漏洞

下面展示的是脚本diag_dns.php中存在命令注入漏洞的代码片。我们可以看到，这段代码首先检查POST参数host是否存在，如果存在的话，就将变量$GET的值赋给变量$POST。然后，代码继续检查GET参数createalias的值是否等于true，如果是的话，则把POST参数host的值的首尾空白符去掉：trim()函数的作用就是去掉字符串首尾处的空白符，但是字符串中间部分的空白符则保持不变。之后，还有一些其他语句，不过它们并不是我们这里关注的重点，我们关心的是如何插入在反引号中运行的命令。

```text
/* Cheap hack to support both $_GET and $_POST */ if ($_GET['host']) $_POST = $_GET; if($_GET['createalias'] == "true") { $host = trim($_POST['host']); if($_GET['override']) $override = true; $a_aliases = &$config['aliases']['alias']; $type = "hostname"; $resolved = gethostbyname($host); if($resolved) { $host = trim($_POST['host']); $dig=`dig "$host" A | grep "$host" | grep -v ";" | awk '{ print 5 }'`;
```
由于我们可以直接操纵变量$host的值，因此，我们完全可以在注入代码的反引号中插入我们想要执行的命令。下面，我们输入以下内容，让它作为POST变量host的输入值：

```text
192.168.1.1";ifconfig>/usr/local/www/temp.txt;echo+"
```
下面展示的是含有上述输入字符串的HTTP请求，具体如图所示。

![原文图片](/media/2016/06/ae8ba378c51fc8a195894e1a863f3748)

图1 发送给服务器的恶意请求
当上面所示的请求被执行时，Web浏览器会显示如下所示的内容，其中含有一个错误信息，出错原因是没有提供有效的主机名。

![原文图片](/media/2016/06/35fdcb492107c858be18b31b552194ae)

图2 错误消息：invalid hostname
我们已经在用户提供的POST参数host放入了一个特殊字符;，这个字符的作用是分隔多个顺序执行的命令。需要注意的是这种情况下，只有当第一条命令返回``（成功返回）的时候，第二条命令才会被执行；因此，只有确保前面的命令全部成功，后面的命令才能得以执行。下面给出一些可以放在反引号中执行的命令：

```text
# dig "192.168.1.1"; # ifconfig>/usr/local/www/temp.txt; # echo+"" A | grep "192.168.1.1"; # ifconfig>/usr/local/www/temp.txt; # echo+"" | grep -v ";" | awk '{ print $5 }'
```
我们会看到，每条命令都会正确执行，其中最重要的命令就是ifconfig了，它已经被注入到了获得输出内容的脚本中了。由于我们无法直接通过响应报文获得这些命令的输出结果，所以必须通过管道命令将其输出到DocumentRoot下的一个文件中，然后才能通过Web浏览器正常访问。下面展示的是[https://pfsense/temp.txt](https://pfsense/temp.txt)中的部分请求内容。

![原文图片](/media/2016/06/e0513a3793ad3cb168ec5179e29d449c)

图3 生成的temp.txt文件
前面，我们已经成功地注入了ifconfig命令并得到了其输出结果，实际上这就意味着我们可以向diag_dns.php脚本输入任意命令并能得到其输出结果。
因此，攻击者可以注入精心构造的命令，并使其在服务器上面执行。

# 0x02 Diag_smart.php脚本中的命令注入

在diag_smart.php脚本中，含有一个名为update_email的函数，代码如下所示。这个函数的作用是编辑smartd.conf文件，来添加或删除通知磁盘出错情况的电子邮件地址。从下面的代码可以看到，这个函数带有一个名为smartmonemail的POST参数。需要注意的是，这个函数会调用sed命令，并将参数unescaped直接传递给shell_exec函数。

```text
function update_email($email) { // Did they pass an email? if(!empty($email)) { // Put it in the smartd.conf file shell_exec("/usr/bin/sed -i old 's/^DEVICESCAN.*/DEVICESCAN -H -m " $email . "/' /usr/local/etc/smartd.conf"); } // Nope Else { // Remove email flags in smartd.conf shell_exec("/usr/bin/sed -i old 's/^DEVICESCAN.*/DEVICESCAN/' usr/local/etc/smartd.conf"); } } if($_POST['email']) { // Write the changes to the smartd.conf file update_email($_POST['smartmonemail']); }
```
实际上，上面的代码存在一个漏洞，允许我们向shell_exec函数注入任意命令，并执行之。下面的请求为我们展示了一个命令注入示例，它将"Command Injection"反射到/var/local/www/cmd.txt文件中。
当shell_exec函数执行时，实际上下列命令也会随之执行。

```text
# /usr/bin/sed -i old 's/^DEVICESCAN.*/DEVICESCAN -H -m ejan/'+/usr/local/etc/lynx.cfg; # echo+"Command+Injection">/usr/local/www/cmd.txt; # echo+' /' /usr/local/etc/smartd.conf;
```
在上面的命令中，第一条和第三条命令只是配角，中间那条命令才是我们想要注入的主角。实际上，我们可以利用shell_exec函数注入任意命令。如果使用浏览器访问[https://pfsense/cmd.txt](https://pfsense/cmd.txt)就会发现，这个字符串实际上已经被保存到DocumentRoot下的相应文件中了。

![原文图片](/media/2016/06/59cb8d05dc2528e29674a1502c07d522)

图4 生成的cmd.txt文件
换句话说，攻击者可以注入任意命令，并使其在服务器上面执行。

# 0x03 Status_rrd_graph_img.php脚本中的命令注入漏洞

在status_rrd_graph_img.php脚本中也存在命令注入漏洞，这主要是由于exec()函数的调用方式引起的，下面是与此漏洞有关的部分代码。

```text
if ($_GET['database']) { $curdatabase = basename($_GET['database']); } else { $curdatabase = "wan-traffic.rrd"; } if(strstr($curdatabase, "queues")) { log_error(sprintf(gettext("failed to create graph from %s%s, emoving database"),$rrddbpath,$curdatabase)); exec("/bin/rm -f $rrddbpath$curif$queues"); Flush(); Usleep(500); enable_rrd_graphing(); } if(strstr($curdatabase, "queuesdrop")) { log_error(sprintf(gettext("failed to create graph from %s%s, emoving database"),$rrddbpath,$curdatabase)); exec("/bin/rm -f $rrddbpath$curdatabase"); Flush(); Usleep(500); enable_rrd_graphing(); }
```
在上述代码的开头部分，会根据GET参数database的设置情况来调用basename函数：如果设置了该参数，则利用它来调用basename函数；否则就被设为静态字符串wan-traffic.rrd。由于我们想要将代码注入到这个脚本中，所以，我们必须将这个参数设为某个值，因为我们必须这样做才能绕过basename函数。此外，basename函数需要一个文件路径作为其参数，其返回值为路径中的文件名部分（不包括扩展名），需要说明的是在Linux /BSD（pfSense）中使用正斜杠/来作为路径分隔符。因此，这个函数返回的内容基本上就是最后一个正斜杠/后面的字符串，这一点在注入参数值时必须考虑到，因为最后一个正斜杠前面的内容都会被删去。 因此，我们可以向GET参数database中注入任意字符，但是正斜杠除外。需要注意的是，我们可以向上面代码中的任何一个exec()语句注入命令，这主要取决于利用GET参数databage传递的字符串——就本例来说，我们使用的是第二个exec()函数调用，因为它要更简单一些。 当上面的底部代码被执行的时候，下列命令也会随之执行。

```text
# /bin/rm -f /var/db/rrd/$curdatabase;
```
我们可以在这个命令的尾部添加字符;，以便插入其他在rm命令运行结束以后需要执行的命令。需要说明的是，如果我们使用了命令分隔符;的话，那么只有当rm命令已经成功执行完成之后，后插入的命令才会被执行。如果我们并不关心rm命令的执行结果的话，我们可以使用&&来分隔命令。需要说明的是，我们无法向任意目录中echo文本，因为这里不允许使用正斜杠。为了克服这个问题，我们可以先通过cd命令进入预定目录，然后通过命令管道实现文本传输的目的。首先，我们必须搞清楚被执行代码的当前目录，这里为/var/db/rrd/directory。下面的请求展示了我们是如何执行queues;echo+"CMD+INJECT">cmd.txt命令的。

![原文图片](/media/2016/06/48e0930303fd9c43fc94b21e076e4e16)

图5 执行echo命令的请求
由于当前目录是/var/db/rrd/，因此，这里创建的cmd.txt文件的内容为CMD INJECT，这可以通过显示该文件的内容来加以检验。

```text
# cat /var/db/rrd/cmd.txt CMD INJECT
```
为了在PfSense安装路径下的DocumentRoot中生成同样的文件，我们可以通过三个cd ..命令返回上层目录，然后切换至 /usr/local/www/ directory目录，并从这里执行echo命令。这样就能够在/usr/local/www/cmd.txt路径生成cmd.txt文件了。

![原文图片](/media/2016/06/e731995fe96c7554f705353332b917b4)

图6 执行echo命令的请求
由于我们当前位于PfSense Web应用的DocumentRoot目录中，所以只要在浏览器中请求cmd.txt，就能知道是否收到了输出CMD INJECT了。

![原文图片](/media/2016/06/c9a04b8b7a4c3a0cce954635d2442300)

图7 生成的cmd.txt文件
这个漏洞允许我们在PfSense服务器上执行任意的代码，因此最终会导致这个防火墙形同虚设。 换句话说，攻击者可以注入任意命令，并使其在服务器上面执行。

# 0x04 小结

本文详细分析了我们在PfSense中发现的一些命令注入漏洞，希望对读者朋友们有所帮助。

## Replies
