1. WPLMS 플러그인 (WordPress Learning Management System)

- WordPress를 사용해 LMS를 구축할 수 있도록 돕는 플러그인

※ Learning Management System : 학습 관리 시스템, 온라인으로 학생들의 학습을 관리할 수 있게 해주는 소프트웨어

2. VibeBP (Vibe BuddyPress Plugin)

- WPLMS와 함께 사용되는 플러그인으로, 강력한 소셜 네트워킹 및 회원 관리 기능을 제공

3. 취약점

3.1 CVE-2024-56043 [2][3]

[사진 1] CVE-2024-56043

- WPLMS의 잘못된 권한 할당으로 인한 권한 상승 취약점 (CVSS: 9.8)

영향받는 버전 : WPLMS <= 1.9.9

 

- includes/vibe-shortcodes/ajaxcalls.php의 wplms_register_user()에 취약점 존재

> wp_ajax_nopriv_wplms_register_user()에 의해 호출되어, 사용자 등록(≒ 회원가입)을 처리하는 함수
> Line7 : 사용자 입력인 $_POST['settings'] 값을 JSON으로 디코딩하여 $settings에 할당
> Line59 ~ Line62 : $setting 객체의 id 값을 확인해 default_role인 경우 $setting 객체의 value 값을 $user_args['role']에 할당
> Line100 : wp_insert_user($user_args)를 사용해 새로운 사용자 생성

※ WordPress의 default_role은 6가지의 값을 가짐 : Super Admin, Administrator, Editor, Author, Contributor, Subscriber [4]

 

- 사용자 입력으로 전달된 default_role에 대한 검증 없이 user_args['role']에 할당되므로, 임의의 역할을 지정해 권한을 상승(Super Admin, Administrator)할 수 있음

includes/vibe-shortcodes/ajaxcalls.php, function wplms_register_user()
1     function wplms_register_user(){
2         if ( !isset($_POST['security']) || !wp_verify_nonce($_POST['security'],'bp_new_signup') || !isset($_POST['settings'])){
3             echo '<div class="message">'.__('Security check Failed. Contact Administrator.','wplms').'</div>';
4             die();
5         }
6         $flag = 0;
7         $settings = json_decode(stripslashes($_POST['settings']));
8         if(empty($settings)){
9             $flag = 1; 
10         }
11     ------------- CUT HERE -------------
12     
13         $user_args = $user_fields = $save_settings = array();
14     
15         if(empty($flag)){
16     
17     ------------- CUT HERE -------------
18     
19             foreach($settings as $setting){
20     
21                 if(!empty($setting->id)){
22                     $settings2[] = $setting->id;
23                     if($setting->id == 'signup_username'){
24                         $user_args['user_login'] = $setting->value;
25                     }else if($setting->id == 'signup_email'){
26                         $user_args['user_email'] = $setting->value;
27                     }else if($setting->id == 'signup_password'){
28                         $user_args['user_pass'] = $setting->value;
29                     }else{
30                         if(strpos($setting->id,'field') !== false){
31     
32                             $f = explode('_',$setting->id);
33                             $field_id = $f[1]; 
34                             if(strpos($field_id, '[')){ //checkbox
35                                 $v = str_replace('[','',$field_id);
36                                 $v = str_replace(']','',$v);
37                                 $field_id = $v;
38                                 if(is_Array($user_fields[$field_id]['value'])){
39                                     $user_fields[$field_id]['value'][] = $setting->value;
40                                 }else{
41                                     $user_fields[$field_id] = array('value'=>array($setting->value));
42                                 }
43                             }else{
44                                 if(is_numeric($field_id) && !isset($f[2])){
45                                     $user_fields[$field_id] = array('value'=>$setting->value);
46                                 }else{
47                                     if(in_array($f[2],array('day','month','year'))){
48                                         $user_fields['field_' . $field_id . '_'.$f[2]] = $setting->value;
49                                     }else{
50                                         $user_fields[$field_id]['visibility']=$setting->value;    
51                                     }
52                                 }
53                             }
54                             
55                         }else{
56                             if(isset($form_settings[$setting->id])){
57                             
58                                 $form_settings[$setting->id] = 0; // use it for empty check 
59                                 if($setting->id=='default_role'){
60                                     $save_settings[$setting->id]=$setting->value;
61                                     $user_args['role'] = $setting->value;
62                                 }
63                                 if($setting->id=='member_type'){
64                                     $save_settings[$setting->id]=$setting->value;
65                                     $member_type=$setting->value;
66                                 }
67                                 if($setting->id=='wplms_user_bp_group'){
68                                     if(in_array($setting->value,$reg_form_settings['settings']['wplms_user_bp_group']) || $reg_form_settings['settings']['wplms_user_bp_group'] === array('enable_user_select_group')){
69                                         $save_settings[$setting->id]=$setting->value;
70                                         $wplms_user_bp_group = $setting->value;
71                                     }else{
72                                         echo '<div class="message_wrap"><div class="message error">'._x('Invalid Group selection','error message when group is not valid','wplms').'<span></span></div></div>';
73                                         die();
74                                     }
75                                     
76                                 }
77                             }
78                             
79                         }
80                     }
81                 }
82             }
83             if(!in_array('wplms_user_bp_group', $settings2)){
84                 if(!empty($reg_form_settings['settings']['wplms_user_bp_group']) && is_array($reg_form_settings['settings']['wplms_user_bp_group']) && $reg_form_settings['settings']['wplms_user_bp_group'] !== array('enable_user_select_group') && count($reg_form_settings['settings']['wplms_user_bp_group'])==1){
85                     $wplms_user_bp_group = $reg_form_settings['settings']['wplms_user_bp_group'][0];
86                 }
87             }
88         }
89     
90     ------------- CUT HERE -------------
91     
92         /*
93         FORM SETTINGS
94         */
95         if(empty($form_settings['hide_username'])){
96             $user_args['user_login'] = $user_args['user_email'];
97         }
98         $user_id = 0;
99         if(empty($form_settings['skip_mail'])){
100             $user_id = wp_insert_user($user_args);
101     
102     ------------- CUT HERE -------------

 

3.2 CVE-2024-56048 [5][6]

[사진 2] CVE-2024-56048

- WPLMS의 권한 검증 누락으로 인한 권한 확대 취약점

영향받는 버전 : WPLMS <= 1.9.9

 

- include/vibe-customtypes/includes/musettings.php의 update_license_key()에 취약점 존재
> Line2 ~ Line5 : 해당 요청이 WordPress 내에서 생성된 유효한 요청인지 확인
> Line6 ~ Line9 : $_POST['addon'] 및 $_POST['key'] 값이 비어있지 않은지 확인
> Line10 : $_POST['addon'] 및 $_POST['key'] 값을 사용해 옵션 값 업데이트

 

각 값에 대한 검증이 누락되어, 권한을 확대할 수 있음
> wp_verify_nonce()에 사용되는 nonce 값은 인증된 사용자의 경우 누구나 검증 우회 가능
> $_POST['addon'] 및 $_POST['key'] 값이 비어있는지만 검증 하므로 임의의 값을 전달할 수 있음
> $_POST['addon'] 및 $_POST['key'] 값을 사용해 원하는 만큼 검증 없이 옵션 값 업데이트 가능

includes/vibe-customtypes/includes/musettings.php, function update_license_key()
1     function update_license_key(){
2     if ( !isset($_POST['security']) || !wp_verify_nonce($_POST['security'],'security')){
3     _e('Security check Failed. Contact Administrator.','wplms');
4     die();
5     }
6     if(empty($_POST['addon']) || empty($_POST['key'])){
7     _e('Unable to update key.','wplms');
8     die();
9     }
10     update_option($_POST['addon'],$_POST['key']);
11     echo apply_filters('wplms_addon_license_key_updated',__('Key Updated.','wplms'));
12     die();
13     }

 

3.3 CVE-2024-56040 [7][8]

[사진 3] CVE-2024-56040

- VibeBP의 잘못된 권한 할당으로 인한 권한 상승 취약점 (CVSS: 9.8)

영향받는 버전 : VibeBP <= 1.9.9.4.1

 

- includes/class.ajax.php의 vibebp_register_user()에 취약점 존재
> wp_ajax_nopriv_wplms_register_user()에 의해 호출되어, 사용자 등록(≒ 회원가입)을 처리하는 함수
> Line7 : 사용자 입력인 $_POST['settings'] 값을 JSON으로 디코딩하여 $settings에 할당
> Line60 ~ Line63 : $setting 객체의 id 값을 확인해 default_role인 경우 $setting 객체의 value 값을 $user_args['role']에 할당
> Line138 : wp_insert_user($user_args)를 사용해 새로운 사용자 생성

 

- 사용자 입력으로 전달된 default_role에 대한 검증 없이 user_args['role']에 할당되므로, 임의의 역할을 지정해 권한을 상승(Super Admin, Administrator)할 수 있음

includes/class.ajax.php, function vibebp_register_user()
1     function vibebp_register_user(){
2         if ( !isset($_POST['security']) || !wp_verify_nonce($_POST['security'],'bp_new_signup') || !isset($_POST['settings'])){
3             echo '<div class="message">'.__('Security check Failed. Contact Administrator.','wplms').'</div>';
4             die();
5         }
6         $flag = 0;
7         $settings = json_decode(stripslashes($_POST['settings']));
8         if(empty($settings)){
9             $flag = 1; 
10         }
11     
12     ------------- CUT HERE -------------
13     
14         $user_args = $user_fields = $save_settings = array();
15     
16         if(empty($flag)){
17     
18     ------------- CUT HERE -------------
19     
20             foreach($settings as $setting){
21     
22                 if(!empty($setting->id)){
23                     $settings2[] = $setting->id;
24                     if($setting->id == 'signup_username'){
25                         $user_args['user_login'] = $setting->value;
26                     }else if($setting->id == 'signup_email'){
27                         $user_args['user_email'] = $setting->value;
28                     }else if($setting->id == 'signup_password'){
29                         $user_args['user_pass'] = $setting->value;
30                     }else{
31                         if(strpos($setting->id,'field') !== false){
32     
33                             $f = explode('_',$setting->id);
34                             $field_id = $f[1]; 
35                             if(strpos($field_id, '[')){ //checkbox
36                                 $v = str_replace('[','',$field_id);
37                                 $v = str_replace(']','',$v);
38                                 $field_id = $v;
39                                 if(is_Array($user_fields[$field_id]['value'])){
40                                     $user_fields[$field_id]['value'][] = $setting->value;
41                                 }else{
42                                     $user_fields[$field_id] = array('value'=>array($setting->value));
43                                 }
44                             }else{
45                                 if(is_numeric($field_id) && !isset($f[2])){
46                                     $user_fields[$field_id] = array('value'=>$setting->value);
47                                 }else{
48                                     if(in_array($f[2],array('day','month','year'))){
49                                         $user_fields['field_' . $field_id . '_'.$f[2]] = $setting->value;
50                                     }else{
51                                         $user_fields[$field_id]['visibility']=$setting->value;    
52                                     }
53                                 }
54                             }
55                             
56                         }else{
57                             if(isset($form_settings[$setting->id])){
58                             
59                                 $form_settings[$setting->id] = 0; // use it for empty check 
60                                 if($setting->id=='default_role'){
61                                     $save_settings[$setting->id]=$setting->value;
62                                     $user_args['role'] = $setting->value;
63                                 }
64                                 if($setting->id=='member_type'){
65                                     $save_settings[$setting->id]=$setting->value;
66                                     $member_type=$setting->value;
67                                 }
68                                 if($setting->id=='vibebp_user_bp_group'){
69                                     if(in_array($setting->value,$reg_form_settings['settings']['vibebp_user_bp_group']) || $reg_form_settings['settings']['vibebp_user_bp_group'] === array('enable_user_select_group')){
70                                         $save_settings[$setting->id]=$setting->value;
71                                         $vibebp_user_bp_group = $setting->value;
72                                     }else{
73                                         echo '<div class="message_wrap"><div class="message error">'._x('Invalid Group selection','error message when group is not valid','wplms').'<span></span></div></div>';
74                                         die();
75                                     }
76                                     
77                                 }
78                             }
79                             
80                         }
81                     }
82                 }
83             }
84             if(!in_array('vibebp_user_bp_group', $settings2)){
85                 if(!empty($reg_form_settings['settings']['vibebp_user_bp_group']) && is_array($reg_form_settings['settings']['vibebp_user_bp_group']) && $reg_form_settings['settings']['vibebp_user_bp_group'] !== array('enable_user_select_group') && count($reg_form_settings['settings']['vibebp_user_bp_group'])==1){
86                     $vibebp_user_bp_group = $reg_form_settings['settings']['vibebp_user_bp_group'][0];
87                 }
88             }
89         }
90     
91     
92     
93         $user_args = apply_filters('vibebp_register_user_args',$user_args);
94         
95     
96         //hook for validations externally
97         do_action('vibebp_custom_registration_form_validations',$name,$settings,$all_form_settings,$user_args);
98         do_action('wplms_custom_registration_form_validations',$name,$settings,$all_form_settings,$user_args);
99     
100         /*
101         RUN CONDITIONAL CHECKS
102         */
103         $check_filter = filter_var($user_args['user_email'], FILTER_VALIDATE_EMAIL); // PHP 5.3
104         if(empty($user_args['user_email']) || empty($user_args['user_pass']) || empty($check_filter)){
105             echo '<div class="message_wrap"><div class="message error">'._x('Invalid Email/Password !','error message when registration form is empty','wplms').'<span></span></div></div>';
106             die();
107         }
108     
109         //Check if user exists
110         if(!isset($user_args['user_email']) || email_exists($user_args['user_email'])){
111             echo '<div class="message_wrap"><div class="message error">'._x('Email already registered.','error message','wplms').'<span></span></div></div>';
112             die();
113         }
114     
115         //Check if user exists
116         if(!isset($user_args['user_login'])){
117     
118             $user_args['user_login'] = $user_args['user_email'];
119             if(email_exists($user_args['user_login'])){
120                 echo '<div class="message_wrap"><div class="message error">'._x('Username already registered.','error message','wplms').'<span></span></div></div>';
121                 die();
122             }
123         }elseif (username_exists($user_args['user_login'])){
124             echo '<div class="message_wrap"><div class="message error">'._x('Username already registered.','error message','wplms').'<span></span></div></div>';
125             die();
126         }
127         
128     ------------- CUT HERE -------------
129     
130         /*
131         FORM SETTINGS
132         */
133         if(empty($form_settings['hide_username'])){
134             $user_args['user_login'] = $user_args['user_email'];
135         }
136         $user_id = 0;
137         if(empty($form_settings['skip_mail'])){
138             $user_id = wp_insert_user($user_args);
139     
140     ------------- CUT HERE -------------

4. 대응방안

- 벤더사 제공 업데이트 적용 [9][10]
> WPLMS Plugin 1.9.9.5.3
> Vibebp 1.9.9.7.7

> 사용자가 등록할 수 있는 역할을 제한하는 패치 적용
> 추가 권한 검사를 구현하고 업데이트할 수 있는 옵션 이름에 대한 허용 목록 검사 적용

5. 참고

[1] https://patchstack.com/articles/multiple-critical-vulnerabilities-patched-in-wplms-and-vibebp-plugins/
[2] https://nvd.nist.gov/vuln/detail/CVE-2024-56043
[3] https://patchstack.com/database/wordpress/plugin/wplms-plugin/vulnerability/wordpress-wplms-plugin-1-9-9-unauthenticated-privilege-escalation-vulnerability
[4] https://developer.wordpress.org/plugins/users/roles-and-capabilities/
[5] https://nvd.nist.gov/vuln/detail/CVE-2024-56048
[6] https://patchstack.com/database/wordpress/plugin/wplms-plugin/vulnerability/wordpress-wplms-plugin-1-9-9-arbitrary-option-update-to-privilege-escalation-vulnerability
[7] https://nvd.nist.gov/vuln/detail/CVE-2024-56040
[8] https://patchstack.com/database/wordpress/plugin/vibebp/vulnerability/wordpress-vibebp-plugin-1-9-9-4-1-unauthenticated-privilege-escalation-vulnerability
[9] https://wplms.io/support/knowledge-base/vibebp-1-9-9-7-7-wplms-plugin-1-9-9-5-2/
[10] https://asec.ahnlab.com/ko/85311/

+ Recent posts