[{"data":1,"prerenderedAt":525},["ShallowReactive",2],{"post-freshshop-cve-2026-33937":3},{"id":4,"title":5,"body":6,"description":509,"extension":510,"meta":511,"navigation":519,"path":520,"seo":521,"stem":522,"__hash__":523,"dateDisplay":517,"image":518,"tags":513,"badge":512,"readingTime":524},"posts\u002Fposts\u002Ffreshshop-cve-2026-33937.md","FreshShop — CVE-2026-33937 Write-up",{"type":7,"value":8,"toc":498},"minimark",[9,25,28,30,35,47,53,56,59,70,72,76,90,93,100,107,109,113,120,127,130,137,140,154,157,164,167,173,175,179,185,192,198,205,253,263,266,273,279,289,291,295,298,304,307,314,317,323,325,329,332,338,344,347,354,361,363,367,374,383,386,393,396,402,412,414,418,421,428,431,453,456,458,462,465,468,475,478,494],[10,11,12],"blockquote",{},[13,14,15,16,20,21,24],"p",{},"Write-up ngắn về quá trình phân tích và tái hiện ",[17,18,19],"strong",{},"CVE-2026-33937"," trong Handlebars thông qua lab FreshShop.\nChủ đề chính: ",[17,22,23],{},"Type Confusion → AST Injection → JavaScript Code Injection → Remote Code Execution",".",[26,27],"hr",{},[26,29],{},[31,32,34],"h2",{"id":33},"_1-giới-thiệu","1. Giới thiệu",[13,36,37,38,42,43,46],{},"Trong quá trình nghiên cứu các lỗ hổng liên quan đến template engine trên Node.js, mình tập trung vào một bug khá thú vị trong Handlebars: khi ",[39,40,41],"code",{},"Handlebars.compile()"," không chỉ nhận template string thông thường, mà còn có thể nhận trực tiếp một ",[17,44,45],{},"AST object"," đã được parse sẵn.",[13,48,49,50,52],{},"Điều này trở nên nguy hiểm nếu ứng dụng nhận dữ liệu từ người dùng, deserialize thành object, rồi truyền thẳng object đó vào ",[39,51,41],{}," mà không kiểm tra kiểu dữ liệu.",[13,54,55],{},"CVE-2026-33937 khai thác chính điểm này.",[13,57,58],{},"Nói ngắn gọn:",[60,61,67],"pre",{"className":62,"code":64,"language":65,"meta":66},[63],"language-text","User input\n   ↓\nJSON.parse()\n   ↓\nAST object\n   ↓\nHandlebars.compile(AST)\n   ↓\nGenerated JavaScript\n   ↓\nnew Function()\n   ↓\nRemote Code Execution\n","text","id=\"x01g3d\"",[39,68,64],{"__ignoreMap":69},"",[26,71],{},[31,73,75],{"id":74},"_2-tổng-quan-cve-2026-33937","2. Tổng quan CVE-2026-33937",[13,77,78,79,82,83,86,87,24],{},"CVE-2026-33937 ảnh hưởng đến package ",[39,80,81],{},"handlebars"," trên npm, trong các phiên bản từ ",[39,84,85],{},"4.0.0"," đến ",[39,88,89],{},"4.7.8",[13,91,92],{},"Thông tin chính:",[60,94,98],{"className":95,"code":96,"language":65,"meta":97},[63],"CVE ID: CVE-2026-33937\nPackage: handlebars\nAffected versions: >= 4.0.0 \u003C= 4.7.8\nPatched version: 4.7.9\nSeverity: Critical\nCWE: CWE-843 Type Confusion, CWE-94 Code Injection\n","id=\"z6l1cq\"",[39,99,96],{"__ignoreMap":69},[13,101,102,103,106],{},"Về bản chất, đây là lỗi ",[17,104,105],{},"Type Confusion"," trong quá trình xử lý AST. Một field đáng lẽ phải là number lại có thể bị attacker thay bằng JavaScript code string. Khi compiler sinh source code, đoạn string này đi vào generated JavaScript và được thực thi.",[26,108],{},[31,110,112],{"id":111},"_3-freshshop-lab","3. FreshShop Lab",[13,114,115,116,119],{},"Để tái hiện lỗ hổng, mình dựng một lab web tên ",[17,117,118],{},"FreshShop",". Đây là một ứng dụng giả lập trang bán hàng có chức năng tài khoản người dùng, chỉnh sửa profile và hiển thị phần mô tả cá nhân.",[13,121,122,123,126],{},"Attack surface chính nằm ở phần ",[17,124,125],{},"About You"," trong trang edit profile.",[13,128,129],{},"Người dùng có thể nhập nội dung vào trường này, lưu lại, sau đó nội dung được render ở trang profile.",[13,131,132],{},[133,134],"img",{"alt":135,"src":136},"FreshShop Lab Interface","\u002Fimages\u002FCVE_2026_33937\u002F01-freshshop-lab-interface.png",[13,138,139],{},"Ban đầu, trường About You trông giống một input bình thường. Tuy nhiên, khi nhập payload template đơn giản như:",[60,141,145],{"className":142,"code":143,"language":81,"meta":144,"style":69},"language-handlebars shiki shiki-themes github-light github-dark","{{this}}\n","id=\"m0r76a\"",[39,146,147],{"__ignoreMap":69},[148,149,152],"span",{"class":150,"line":151},"line",1,[148,153,143],{},[13,155,156],{},"và truy cập lại trang profile, ứng dụng trả về:",[60,158,162],{"className":159,"code":160,"language":65,"meta":161},[63],"[object Object]\n","id=\"jx6fsp\"",[39,163,160],{"__ignoreMap":69},[13,165,166],{},"Kết quả này cho thấy nội dung người dùng nhập vào đang được xử lý bởi template engine, thay vì chỉ hiển thị raw text.",[13,168,169],{},[133,170],{"alt":171,"src":172},"Template Processing Check","\u002Fimages\u002FCVE_2026_33937\u002F02-template-processing-check.png",[26,174],{},[31,176,178],{"id":177},"_4-root-cause","4. Root cause",[13,180,181,182,184],{},"Root cause nằm ở việc ",[39,183,41],{}," có thể nhận AST object trực tiếp.",[13,186,187,188,191],{},"Trong flow bình thường, template string sẽ được parse thành AST nội bộ. Nhưng nếu input đã là một object có dạng ",[39,189,190],{},"Program",", Handlebars có thể bỏ qua bước parse và dùng trực tiếp AST đó.",[13,193,194,195,24],{},"Vấn đề nằm ở node ",[39,196,197],{},"NumberLiteral",[13,199,200,201,204],{},"Về mặt logic, node này phải có ",[39,202,203],{},"value"," là number:",[60,206,211],{"className":207,"code":208,"language":209,"meta":210,"style":69},"language-json shiki shiki-themes github-light github-dark","{\n  \"type\": \"NumberLiteral\",\n  \"value\": 1\n}\n","json","id=\"a8n4tz\"",[39,212,213,219,236,247],{"__ignoreMap":69},[148,214,215],{"class":150,"line":151},[148,216,218],{"class":217},"sVt8B","{\n",[148,220,222,226,229,233],{"class":150,"line":221},2,[148,223,225],{"class":224},"sj4cs","  \"type\"",[148,227,228],{"class":217},": ",[148,230,232],{"class":231},"sZZnC","\"NumberLiteral\"",[148,234,235],{"class":217},",\n",[148,237,239,242,244],{"class":150,"line":238},3,[148,240,241],{"class":224},"  \"value\"",[148,243,228],{"class":217},[148,245,246],{"class":224},"1\n",[148,248,250],{"class":150,"line":249},4,[148,251,252],{"class":217},"}\n",[13,254,255,256,259,260,262],{},"Nhưng trong phiên bản vulnerable, compiler không kiểm tra chặt kiểu dữ liệu của ",[39,257,258],{},"NumberLiteral.value",". Vì vậy attacker có thể biến ",[39,261,203],{}," thành một chuỗi JavaScript.",[13,264,265],{},"Luồng lỗi có thể tóm tắt như sau:",[60,267,271],{"className":268,"code":269,"language":65,"meta":270},[63],"NumberLiteral.value\n   ↓ không validate typeof\npushLiteral()\n   ↓\nGenerated JavaScript source\n   ↓\nnew Function(source)\n   ↓\nJavaScript code execution\n","id=\"ntf2qk\"",[39,272,269],{"__ignoreMap":69},[13,274,275],{},[133,276],{"alt":277,"src":278},"AST Injection Root Cause","\u002Fimages\u002FCVE_2026_33937\u002F03-ast-injection-root-cause.svg",[13,280,281,282,285,286,24],{},"Điểm quan trọng là đây không phải lỗi template injection kiểu thông thường. Lỗ hổng này xảy ra sâu hơn, ở tầng ",[17,283,284],{},"AST compiler"," và ",[17,287,288],{},"JavaScript code generation",[26,290],{},[31,292,294],{"id":293},"_5-luồng-khai-thác-trong-lab","5. Luồng khai thác trong lab",[13,296,297],{},"Trong lab FreshShop, attacker không chỉ gửi text bình thường vào trường About You. Thay vào đó, attacker gửi một AST object được craft sẵn.",[13,299,300,301,303],{},"AST này vẫn có cấu trúc hợp lệ theo Handlebars, nhưng phần ",[39,302,258],{}," bị thay bằng JavaScript expression để thoát khỏi context ban đầu và gọi API của Node.js.",[13,305,306],{},"Flow khai thác:",[60,308,312],{"className":309,"code":310,"language":65,"meta":311},[63],"Attacker nhập crafted AST vào About You\n   ↓\nServer lưu payload\n   ↓\nProfile page render lại nội dung\n   ↓\nPayload được đưa vào Handlebars.compile()\n   ↓\nCompiler sinh JavaScript source\n   ↓\nnew Function() compile source\n   ↓\nPayload chạy trong Node.js process\n","id=\"pnri69\"",[39,313,310],{"__ignoreMap":69},[13,315,316],{},"Trong quá trình kiểm thử, payload được gửi qua Burp để quan sát request\u002Fresponse rõ ràng hơn.",[13,318,319],{},[133,320],{"alt":321,"src":322},"Burp Request Payload","\u002Fimages\u002FCVE_2026_33937\u002F04-burp-request-payload.png",[26,324],{},[31,326,328],{"id":327},"_6-xác-nhận-rce","6. Xác nhận RCE",[13,330,331],{},"Sau khi payload được lưu và trang profile được render lại, nội dung phần About không còn là text thông thường nữa. Server trả về output từ command chạy trong môi trường Node.js.",[13,333,334,335,24],{},"Trong PoC, output chứng minh server đã đọc được thông tin từ filesystem, ví dụ ",[39,336,337],{},"\u002Fetc\u002Fpasswd",[13,339,340],{},[133,341],{"alt":342,"src":343},"RCE Output In Profile","\u002Fimages\u002FCVE_2026_33937\u002F05-rce-output-profile.png",[13,345,346],{},"Điều này xác nhận chain đã hoàn chỉnh:",[60,348,352],{"className":349,"code":350,"language":65,"meta":351},[63],"User-controlled input\n→ AST injection\n→ Handlebars compiler\n→ Generated JavaScript\n→ Node.js execution\n→ Command output returned\n","id=\"n5x30r\"",[39,353,350],{"__ignoreMap":69},[13,355,356,357,360],{},"Ở mức bảo mật, đây là ",[17,358,359],{},"Remote Code Execution"," vì attacker đã chuyển từ việc kiểm soát nội dung profile sang thực thi code trên server.",[26,362],{},[31,364,366],{"id":365},"_7-patch-analysis","7. Patch analysis",[13,368,369,370,373],{},"Ở phiên bản ",[39,371,372],{},"4.7.9",", Handlebars bổ sung bước validate AST trước khi compile.",[13,375,376,377,379,380,382],{},"Thay vì tin tưởng trực tiếp AST object được truyền vào, bản vá kiểm tra kiểu dữ liệu của các node quan trọng. Với ",[39,378,197],{},", ",[39,381,203],{}," bắt buộc phải là number hợp lệ.",[13,384,385],{},"Logic patch có thể hiểu như sau:",[60,387,391],{"className":388,"code":389,"language":65,"meta":390},[63],"Nếu node.type === \"NumberLiteral\"\n   ↓\nKiểm tra typeof node.value === \"number\"\n   ↓\nNếu không phải number → throw exception\n   ↓\nPayload bị chặn trước khi vào compiler pipeline\n","id=\"f8tb67\"",[39,392,389],{"__ignoreMap":69},[13,394,395],{},"Khi chạy lại cùng payload trên phiên bản đã vá, ứng dụng trả về lỗi validate thay vì thực thi payload.",[13,397,398],{},[133,399],{"alt":400,"src":401},"Patched Version Blocks Payload","\u002Fimages\u002FCVE_2026_33937\u002F06-patched-version-blocks-payload.png",[13,403,404,405,408,409,24],{},"Điểm đáng chú ý là payload bị chặn ngay từ entry point của AST validation, nên không còn cơ hội đi tới ",[39,406,407],{},"pushLiteral()"," hoặc ",[39,410,411],{},"new Function()",[26,413],{},[31,415,417],{"id":416},"_8-security-impact","8. Security impact",[13,419,420],{},"CVE-2026-33937 có impact nghiêm trọng vì attacker có thể đạt được RCE trên server Node.js nếu ứng dụng thỏa các điều kiện sau:",[60,422,426],{"className":423,"code":424,"language":65,"meta":425},[63],"Ứng dụng dùng handlebars \u003C= 4.7.8\nỨng dụng nhận input do user kiểm soát\nInput được deserialize thành object\nObject được truyền vào Handlebars.compile()\nKhông validate typeof input === \"string\"\n","id=\"ch3drw\"",[39,427,424],{"__ignoreMap":69},[13,429,430],{},"Trong kịch bản thực tế, attacker có thể:",[432,433,434,438,441,444,447,450],"ul",{},[435,436,437],"li",{},"Đọc file nhạy cảm trong container\u002Fserver.",[435,439,440],{},"Truy xuất environment variables.",[435,442,443],{},"Thu thập thông tin cấu hình ứng dụng.",[435,445,446],{},"Gọi command hệ thống.",[435,448,449],{},"Pivot sang service nội bộ nếu server có quyền truy cập.",[435,451,452],{},"Gây ảnh hưởng đến confidentiality, integrity và availability.",[13,454,455],{},"Nếu ứng dụng chạy bằng quyền cao, ví dụ root trong Docker container, impact sẽ còn nghiêm trọng hơn.",[26,457],{},[31,459,461],{"id":460},"_9-kết-luận","9. Kết luận",[13,463,464],{},"CVE-2026-33937 là một ví dụ hay về lỗi xảy ra ở ranh giới giữa template rendering và compiler internals.",[13,466,467],{},"Bài học chính:",[60,469,473],{"className":470,"code":471,"language":65,"meta":472},[63],"Không truyền object không tin cậy vào Handlebars.compile()\nKhông cho user kiểm soát AST\nLuôn validate input type trước khi compile\u002Frender\nUpgrade handlebars lên 4.7.9 hoặc mới hơn\nGiới hạn quyền runtime của Node.js process\n","id=\"u1shii\"",[39,474,471],{"__ignoreMap":69},[13,476,477],{},"Điểm nguy hiểm nhất của bug này là ứng dụng có thể trông rất bình thường: chỉ là một form profile cho người dùng nhập mô tả cá nhân. Nhưng nếu dữ liệu đó đi vào template compiler không an toàn, attacker có thể biến một input text thành RCE.",[13,479,480,481,379,484,379,487,379,490,493],{},"Đây cũng là lý do các hàm dạng ",[39,482,483],{},"compile",[39,485,486],{},"render",[39,488,489],{},"eval",[39,491,492],{},"Function",", hoặc bất kỳ cơ chế code generation nào luôn cần được xem là high-risk sink khi audit bảo mật.",[495,496,497],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":69,"searchDepth":221,"depth":221,"links":499},[500,501,502,503,504,505,506,507,508],{"id":33,"depth":221,"text":34},{"id":74,"depth":221,"text":75},{"id":111,"depth":221,"text":112},{"id":177,"depth":221,"text":178},{"id":293,"depth":221,"text":294},{"id":327,"depth":221,"text":328},{"id":365,"depth":221,"text":366},{"id":416,"depth":221,"text":417},{"id":460,"depth":221,"text":461},"Phân tích lỗ hổng Handlebars AST Injection dẫn tới RCE trên FreshShop Lab. Bao gồm kiểm tra template rendering, khai thác NumberLiteral.value và xác nhận thực thi lệnh trên server.","md",{"badge":512,"tags":513,"date":516,"dateDisplay":517,"image":518},"CVE Analysis",[514,515],"CVE","RCE","2026-06-19","Jun 19, 2026","\u002Fthumbnails\u002Ffreshshoplogo.png",true,"\u002Fposts\u002Ffreshshop-cve-2026-33937",{"title":5,"description":509},"posts\u002Ffreshshop-cve-2026-33937","48469PWU5KK3qB76ZkntpiCnn04eh4y-MNhqAh0OgBE",6,1787385493752]