1. Fancy Product Designer

- 온라인에서 의류, 머그컵, 휴대폰 케이스 등을 사용자 맞춤형으로 디자인할 수 있게 해주는 Worpress 플러그인 [2]

2. 취약점

2.1 CVE-2024-51818 [3]

- 취약점은 class-wc-dokan.php의 get_products_sql_attrs() 함수에 존재
> 해당 함수는 class-product.php의 get_products()에 의해 호출
> Line13 : $attrs를 매개변수로 fpd_get_products_sql_attrs() 호출

 

- get_products_sql_attrs()
> Line23 : fpd_filter_users_select 값이 존재하고, -1이 아닌 경우 if문 실행
> Line24 : "user_id=" 문자열 뒤 strip_tags($_POST['fpd_filter_users_select'])를 추가한 결과를 $where 변수에 할당

 

strip_tags()는 NULL bytes와 HTML 및 PHP 태그를 제거하는 함수로 SQL 공격을 방지하지 못함 [4]
> Line29~31 : $where 값은 get_products()의 $wpdb->get_results로 쿼리에 실행

[사진 1] strip_tags()

inc/api/class-product.php, function get_products()
1     public static function get_products( $attrs = array(), $type = 'catalog' ) {
2     
3     global $wpdb;
4     
5     $defaults = array(
6     'cols' => '*',
7     'where' => '',
8     'order_by' => '',
9     'limit' => null,
10     'offset' => null
11     );
12     
13     $attrs = apply_filters( 'fpd_get_products_sql_attrs', $attrs );
14     
15     extract( array_merge( $defaults, $attrs ) );
16     
17     $products = array();
18     if( fpd_table_exists(FPD_PRODUCTS_TABLE) ) {
19     
20     $where = empty($where) ? $wpdb->prepare( 'WHERE type="%s"', $type) : $wpdb->prepare( 'WHERE type="%s" AND ', $type ) . $where;
21     
22     if( !preg_match('/^[a-zA-Z]+\\s(ASC|DESC)$/', $order_by) )
23     $order_by = '';
24     $order_by = empty($order_by) ? '' : 'ORDER BY '. $order_by;
25     
26     $limit = empty($limit) ? '' : $wpdb->prepare( 'LIMIT %d', $limit );
27     $offset = empty($offset) ? '' : $wpdb->prepare( 'OFFSET %d', $offset );
28     
29     $products = $wpdb->get_results(
30     SELECT $cols FROM .FPD_PRODUCTS_TABLE." $where $order_by $limit $offset"
31     );
32     
33     }
34     
35     return $products;
36     
37     }

woo/class-wc-dokan.php, function get_products_sql_attrs
1     public function get_products_sql_attrs( $attrs ) {
2     
3     $where = isset( $attrs['where'] ) ? $attrs['where'] : null;
4     
5     if( self::user_is_vendor() ) {
6     
7     $user_ids = array(get_current_user_id());
8     
9     //add fpd products from user
10     $fpd_products_user_id = fpd_get_option( 'fpd_wc_dokan_user_global_products' );
11     
12     //skip if no use is set or on product builder
13     if( $fpd_products_user_id !== 'none' && !(isset( $_GET['page'] ) && $_GET['page'] === 'fpd_product_builder') )
14     array_push( $user_ids, $fpd_products_user_id );
15     
16     $user_ids = join( ",", $user_ids );
17     
18     $where = empty($where) ? "user_id IN ($user_ids)" : $where." AND user_id IN ($user_ids)";
19     
20     }
21     
22     //manage products filter
23     if( isset($_POST['fpd_filter_users_select']) && $_POST['fpd_filter_users_select'] != "-1" ) {
24     $where = "user_id=".strip_tags( $_POST['fpd_filter_users_select'] );
25     
26     
27     $attrs['where'] = $where;
28     
29     return $attrs;
30     
31     }

 

2.2 CVE-2024-51919 [5]

- 취약점은 class-pro-export.php의 save_remote_file() 함수와 fpd-admin-functions.php의 fpd_admin_copy_file() 함수에 존재

 

- save_remote_file()
> Line9 : $remote_file_url을 통해 원격 URL 값을 받아 fpd_admin_copy_file() 호출

 

- fpd_admin_copy_file()
> Line8 : basename($file_url)의 결과를 $filename에 할당
> Line10 ~ Line22 : 파일을 복사 또는 저장
파일에 대한 검사 없이 복사 또는 저장하므로 임의의 파일 업로드가 가능

pro-export/class-pro-export.php, function save_remote_file()
1     public static function save_remote_file( $remote_file_url ) {
2     
3         $unique_dir = time().bin2hex(random_bytes(16));
4         $temp_dir = FPD_ORDER_DIR . 'print_ready_files/' . $unique_dir;
5         mkdir($temp_dir);
6     
7         $local_file_path = $temp_dir;
8     
9         $filename = fpd_admin_copy_file(
10             $remote_file_url,
11             $local_file_path
12         );
13     
14         return $filename ? $unique_dir . '/' . $filename : null;
15     
16     }

admin/fpd-admin-functions.php, function fpd_admin_copy_file()
1     function fpd_admin_copy_file( $file_url, $destination_dir ) {
2     
3     if( empty( $file_url ) ) return false;
4     
5     if( !file_exists($destination_dir) )
6             wp_mkdir_p( $destination_dir );
7     
8     $filename = basename( $file_url );
9     
10     if( function_exists('copy') ) {
11     
12     return copy( $file_url, $destination_dir . '/' . $filename ) ? $filename : false;
13     
14     }
15     else {
16     
17     $content = file_get_contents( $file_url );
18     $fp = fopen( $destination_dir . '/' . $filename, 'w' );
19     $bytes = fwrite( $fp, $content );
20     fclose( $fp );
21     
22     return $bytes !== false ? $filename : false;
23     
24     }

3. 대응방안

- 취약점이 벤더사에 전달 되었으나, 최근 버전(6.4.3)까지 패치가 이루어지지 않은 상태
> 권고사항
ⓐ임의 파일 업로드 방지 : 안전한 파일 확장자만 허용하는 허용 목록(allowlist) 설정
ⓑ SQL 인젝션 대응 : 데이터베이스 쿼리의 철저한 입력 값 검증 및 적절한 이스케이프 처리
ⓒ 정기적인 보안 점검 : 플러그인 업데이트 상태 주기적 확인 및 새로운 취약점 발생 여부 모니터링
ⓓ 대안 플러그인 고려 : 개발사가 문제를 해결하지 않는 상황에서 보안이 보장된 대안 플러그인을 사용 고려

4. 참고

[1] https://patchstack.com/articles/critical-vulnerabilities-found-in-fancy-product-designer-plugin/
[2] https://fancyproductdesigner.com/
[3] https://patchstack.com/database/wordpress/plugin/fancy-product-designer/vulnerability/wordpress-fancy-product-designer-plugin-6-4-3-unauthenticated-sql-injection-vulnerability
[4] https://www.php.net/manual/en/function.strip-tags.php
[5] https://patchstack.com/database/wordpress/plugin/fancy-product-designer/vulnerability/wordpress-fancy-product-designer-plugin-6-4-3-unauthenticated-arbitrary-file-upload-vulnerability
[6] https://www.dailysecu.com/news/articleView.html?idxno=162891

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

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

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

2. 취약점

2.1 CVE-2024-56046 [2][3]

[사진 1] CVE-2024-56046

- WPLMS에서 발생하는 파일 업로드 취약점 (CVSS: 10.0)

영향받는 버전 : WPLMS <= 1.9.9

 

- includes/vibe-shortcodes/shortcodes.php의 wplms_form_uploader_plupload()에 취약점 존재
> Line9 : $_REQUEST["name"] 값을 우선적으로 $fileName에 할당하며, 해당 값이 없을 경우 $_FILES["file"]["name"] 값을 사용
> Line17 : $fileName은 파일 저장 경로를 결정하는데 사용됨

 

- name 파라미터는 사용자 요청으로부터 추출 (Line9)
> 해당 값에 대한 검증 없이 사용하여 악의적인 파일(Ex. "../../../attack.php")을 사용해 파일을 업로드할 수 있음

 

- $fileName을 기반으로 서버의 특정 경로에 저장
> 해당 값에 대한 검증이 없어 임의 디렉터리에 악의적인 파일을 업로드할 수 있음

includes/vibe-shortcodes/shortcodes.php, function wplms_form_uploader_plupload()
1     function wplms_form_uploader_plupload(){
2       check_ajax_referer('wplms_form_uploader_plupload');
3     
4       if (empty($_FILES) || $_FILES['file']['error']) {
5           die('{"OK": 0, "info": "Failed to move uploaded file."}');
6       }
7       $chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
8       $chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0;
9       $fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : $_FILES["file"]["name"];
10     
11       $upload_dir_base = wp_upload_dir();
12       $folderPath = $upload_dir_base['basedir']."/wplms_form_uploader";
13       if(function_exists('is_dir') && !is_dir($folderPath)){
14           if(function_exists('mkdir')) 
15               mkdir($folderPath, 0755, true) || chmod($folderPath, 0755);
16       }
17       $filePath = $folderPath."/$fileName";
18     
19       // Open temp file
20       if($chunk == 0) 
21           $perm = "wb" ;
22       else 
23           $perm = "ab";
24     
25       $out = @fopen("{$filePath}.part",$perm );
26     
27       if ($out) {
28         // Read binary input stream and append it to temp file
29         $in = @fopen($_FILES['file']['tmp_name'], "rb");
30         
31         if ($in) {
32           while ($buff = fread($in, 4096))
33             fwrite($out, $buff);
34         } else
35           die('{"OK": 0, "info": "Failed to open input stream."}');
36         
37         @fclose($in);
38         @fclose($out);
39         
40         @unlink($_FILES['file']['tmp_name']);
41       } else
42         die('{"OK": 0, "info": "Failed to open output stream."}');
43     
44       // Check if file has been uploaded
45       if (!$chunks || $chunk == $chunks - 1) {
46         // Strip the temp .part suffix off
47           rename("{$filePath}.part", $filePath);
48           
49       }
50       die('{"OK": 1, "info": "Upload successful."}');
51       exit;
52     }

 

2.2 CVE-2024-56050 [4][5]

[사진 2] CVE-2024-56050

- WPLMS에서 발생하는 파일 업로드 취약점 (CVSS: 9.9)

영향받는 버전 : WPLMS < 1.9.9.5.3

 

- includes/vibe-shortcodes/upload_handler.php의 wp_ajax_zip_upload()에 취약점 존재
> Line4 ~ Line8 : 사용자 요청에서 값을 추출해 변수 할당
> Line18 ~ Line19 : Zip 파일 내 다른 파일이 있는 경우 extractZip()을 통해 파일 내 모든 내용을 추출
> 사용자 요청에서 추출한 값을 검증없이 사용하여 취약점 발생

 

extractZip()
> Line6 : extractTo()를 사용해 Zip 파일내 모든 파일을 $target 디렉터리에 추출
파일에 대한 검증없이 추출되어 취약점 발생
> attack.php 등의 악의적 파일을 포함한 Zip 파일을 업로드할 수 있는 문제 발생

includes/vibe-shortcodes/upload_handler.php, function wp_ajax_zip_upload()
1     function wp_ajax_zip_upload(){
2     $arr = array();
3     
4     $file = $_FILES['uploadedfile']['tmp_name'];
5     $dir = explode(".",$_FILES['uploadedfile']['name']);
6     $dir[0] = str_replace(" ","_",$dir[0]);
7     $target = $this->getUploadsPath().$dir[0];
8     $index = count($dir) -1;
9     
10     if (!isset($dir[$index]) || $dir[$index] != "zip")
11     $arr[0] = __('The Upload file must be zip archive','wplms');
12     else{
13     while(file_exists($target)){
14     $r = rand(1,10);
15     $target .= $r;
16     $dir[0] .= $r;
17     }
18     if (!empty($file))
19     $arr = $this->extractZip($file,$target,$dir[0]);
20     else
21     $arr[0] = __('File too big','wplms');
22     }
23     echo json_encode($arr);
24     die();
25     }

includes/vibe-shortcodes/upload_handler.php, function extractZip()
1     function extractZip($fileName,$target,$dir){
2     $arr = array();
3     $zip = new ZipArchive;
4     $res = $zip->open($fileName);
5     if ($res === TRUE) {
6     $zip->extractTo($target);
7     $zip->close();
8     $file = $this->getFile($target);
9     ;
10     if($file){
11     $arr[0] = 'uploaded'; 
12     $arr[1] = $this->getUploadsUrl().$dir."/".$file; 
13     $arr[2] = $dir;
14     $arr[3] =$file;
15     $arr[4] = $this->getUploadsPath().$dir; 
16     }else{
17     $arr[0] = __('Please upload zip file, Index.html file not found in package','wplms').$target.print_r($file);
18     $this->rrmdir($target);
19     }
20     }else{
21     $arr[0] = __('Upload failed !','wplms');;
22     }
23     return  $arr;
24     }

 

2.3 CVE-2024-56052 [6][7]

[사진 3] CVE-2024-56052

- WPLMS에서 발생하는 파일 업로드 취약점 (CVSS: 9.9)

영향받는 버전 : WPLMS < 1.9.9.5.3

 

- includes/assignments/assignments.php의 wplms_assignment_plupload()에 취약점 존재
> Line2 ~ Line4 : WordPress 내에서 생성된 요청인지와 로그인 유무를 검증
> Line18 : $user_id 및 $assignment_id를 기반으로 $folderPath 생성

 

- $assignment_id에 대한 유효성 검증이 없어 임의 디렉터리에 악의적인 파일을 업로드할 수 있음

includes/assignments/assignments.php, function wplms_assignment_plupload()
1     function wplms_assignment_plupload(){
2       check_ajax_referer('wplms_assignment_plupload');
3       if(!is_user_logged_in())
4           die('user not logged in');
5     
6       $user_id = get_current_user_id();
7       
8       if (empty($_FILES) || $_FILES['file']['error']) {
9         die('{"OK": 0, "info": "Failed to move uploaded file."}');
10       }
11     
12       $chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
13       $chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0;
14       $fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : $_FILES["file"]["name"];
15       
16       $upload_dir_base = wp_upload_dir();
17       $assignment_id = $_POST['assignment_id'];
18       $folderPath = $upload_dir_base['basedir']."/wplms_assignments_folder/".$user_id.'/'.$assignment_id;
19       if(function_exists('is_dir') && !is_dir($folderPath)){
20           if(function_exists('mkdir')) 
21               mkdir($folderPath, 0755, true) || chmod($folderPath, 0755);
22       }
23     
24     
25       $filePath = $folderPath."/$fileName";
26         /*if(function_exists('file_exists') && file_exists($filePath)){
27           echo __(' Chunks upload error ','wplms'). $fileName.__(' already exists.Please rename your file and try again ','wplms');
28           die();
29         }*/
30       // Open temp file
31       if($chunk == 0) $perm = "wb" ;
32       else $perm = "ab";
33     
34       $out = @fopen("{$filePath}.part",$perm );
35     
36       if ($out) {
37         // Read binary input stream and append it to temp file
38         $in = @fopen($_FILES['file']['tmp_name'], "rb");
39         
40         if ($in) {
41           while ($buff = fread($in, 4096))
42             fwrite($out, $buff);
43         } else
44           die('{"OK": 0, "info": "Failed to open input stream."}');
45         
46         @fclose($in);
47         @fclose($out);
48         
49         @unlink($_FILES['file']['tmp_name']);
50       } else
51         die('{"OK": 0, "info": "Failed to open output stream."}');
52         
53         
54       // Check if file has been uploaded
55       if (!$chunks || $chunk == $chunks - 1) {
56         // Strip the temp .part suffix off
57           rename("{$filePath}.part", $filePath);
58           
59       }
60       die('{"OK": 1, "info": "Upload successful."}');
61       exit;
62     }

3. 대응방안

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

> 파일 이름과 유형을 확인하여 업로드할 수 있는 파일을 제한하는 패치 적용
> 영향을 받는 기능에 대한 추가 권한 확인을 구현하거나 영향을 받는 코드 제거

4. 참고

[1] https://patchstack.com/articles/multiple-critical-vulnerabilities-patched-in-wplms-and-vibebp-plugins/
[2] https://nvd.nist.gov/vuln/detail/CVE-2024-56046
[3] https://patchstack.com/database/wordpress/plugin/wplms-plugin/vulnerability/wordpress-wplms-plugin-1-9-9-unauthenticated-arbitrary-file-upload-vulnerability
[4] https://nvd.nist.gov/vuln/detail/CVE-2024-56050
[5] https://patchstack.com/database/wordpress/plugin/wplms-plugin/vulnerability/wordpress-wplms-plugin-1-9-9-5-3-subscriber-arbitrary-file-upload-vulnerability
[6] https://nvd.nist.gov/vuln/detail/CVE-2024-56052
[7] https://patchstack.com/database/wordpress/plugin/wplms-plugin/vulnerability/wordpress-wplms-plugin-1-9-9-5-2-student-arbitrary-file-upload-vulnerability
[8] https://wplms.io/support/knowledge-base/vibebp-1-9-9-7-7-wplms-plugin-1-9-9-5-2/
[9] https://asec.ahnlab.com/ko/85311/

침해 사고 정보
일자 2021/06/05 ~ 2021/06/11
침해 정보 - 환자정보: 병원등록번호, 성명, 생년월일, 성별, 나이, 진료과, 진단명, 검사일, 검사명, 검사결과
- 직원정보: 사번, 성명, 주민번호, 거주지연락처, 연락처, 이메일, 근무부서정보, 직급연차정보, 임용퇴직정보, 휴복직정보, 자격면허정보 등
특징 파일 업로드 취약점을 이용한 웹쉘 업로드로
피해크기 - 약 83만명의 개인정보
> 환자 81만여명
> 전·현직 직원 1만7000여명 
침해 사고 분석
경위 21.05 ~ 21.06 국내·외에 소재한 서버 7대를 장악해 공격 기반 마련
> 국내 4대·해외 3대

② 웹쉘 업로드
> 내부망에서 사용하기 위한 계정 생성
> ID/PW : default/다치지 말라

③ 공유폴더와 연결된 서울대병원 내부망에 침입

④ 병리검사 서버에서 개인정보 탈취
> 81만명의 진료정보를 탈취

⑤ 내부망 전자사보DB에서 개인정보 탈취
> 1만 7000여명의 직원정보 탈취
> 이중 2000명의 정보는 실제 유출된 것으로 확인
원인 ① 파일 업로드가 가능한 병원 내부망의 보안 취약점을 활용
> 웹서버에 명령을 실행해 관리자 권한을 획득할 수 있는 웹쉘이 필터링 되지 않고 업로드 됨
조치 ① 21.07.06 서울대병원 침해 사실 최초 인지 및 공지 게시
- 교육부/보건복지부/개인정보보호위원회/사이버수사대 등에 신고후 조사를 진행
- 서울대병원의 후속조치
> 해당 IP 및 접속 경로 차단
> 서비스 분리
> 취약점 점검 및 보완조치
> 모니터링 강화
> 사용자 PC 비밀번호 변경
> 개인정보보호위원회 등 관련 유관기관 신고
> 병원에 등록된 휴대전화번호로 개인정보 유출 사실 개별 연락 수행

② 23.05.10 경찰청 발표
- 피해기관에 침입 및 정보 유출 수법과 재발 방지를 위한 보안 권고사항을 설명
- 관계기관에 북한 해킹조직의 침입 수법·해킹 도구 등 관련 정보를 제공
> 국가 배후의 조직적 사이버 공격에 대해 치안 역량을 총동원하여 적극적으로 대응
> 관계기관 정보공유 및 협업을 통해 추가적인 피해를 방지
> 사이버 안보를 굳건히 지키기 위해 노력할 계획

- 경찰
> 개인정보보호위원회 의결 결과에 따라 병원 개인정보 보호책임자 입건 여부 검토 예정

③ 23.05.10 개인정보보호위원회
- 전체회의를 통해 서울대학교병원에 과징금 7,475만원 부과 의결
> 서울대병원이 이미 널리 알려진 해킹공격(웹쉘)을 탐지하고, 방어할 관리적·기술적 조치가 미비했다고 판단
> 공공기관 최초로 과징금을 부과
기타 ① 기존 북한발로 확인된 다수 사건과 비교 결과 해당 사건 또한 북한발(김수키, Kimsuky)로 확인
- 기존 사례와 동일한 사항
> 공격 근원지 IP, IP 주소 세탁 기법: 과거 북한 해킹 조직이 사용했던 IP 포함
> 인터넷 사이트 가입정보, 시스템 침입·관리 수법: 해킹용 서버의 사용자 이름·이메일이 과거 북한 해킹조직이 사용한 정보
> 북한어휘 사용: 내부망에 생성한 계정의 비밀번호가 한글 자판으로 '다치지 말라'

② 이승운 경찰청 사이버테러수사대장
- 해킹 조직이 병리검사가 저장됐던 서버를 해킹
- 고위 인사의 개인정보를 빼내기 위한 목적으로 추정

③ 남석 개인정보위 조사조정국장
> 공공기관의 경우 다량의 개인정보를 처리
> 작은 위반행위로도 심각한 피해로 이어질 가능성이 큰 만큼 담당자들의 세심한 주의가 필요

④ 의료 분야 외 다른 분야 또한 주요 정보통신망에 대한 침입 시도가 지속될 것으로 예상
- 최신 보안 업데이트 적용
- 불법적인 접속시도에 대한 접근통제
- 개인정보를 포함한 중요 전산 자료 암호화 등 보안 시스템과 보안정책 강화 권고

 

'침해사고 > 개인정보' 카테고리의 다른 글

유안타증권 개인정보 유출  (0) 2023.07.21
해병대 개인정보 유출  (1) 2023.06.16
인터파크 개인정보 유출  (0) 2023.04.16
한국남부발전 개인정보 유출  (0) 2022.08.31
빗썸 개인정보 유출  (0) 2022.08.24

+ Recent posts