├── .github └── workflows │ ├── release.md │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── cmd └── xssfinder │ └── xssfinder.go ├── docs ├── bypass-headless-detect.png ├── dingbot-demo.png ├── logo.png ├── mitm-demo.png └── notify-demo.png ├── go.mod ├── go.sum ├── internal ├── app │ └── app.go ├── logger │ ├── logger.go │ └── logger_test.go ├── options │ └── options.go └── runner │ ├── runner.go │ └── worker.go ├── pkg ├── chrome │ ├── browser │ │ ├── browser.go │ │ └── browser_test.go │ ├── bypass │ │ ├── bypass.go │ │ ├── bypass_test.go │ │ └── stealth.min.js │ ├── cookies │ │ └── cookies.go │ └── xss │ │ ├── checker │ │ ├── checker.go │ │ └── checker_test.go │ │ └── dom │ │ ├── dom.go │ │ ├── dom_test.go │ │ ├── fuzz.go │ │ ├── fuzz_test.go │ │ ├── hookparse.go │ │ ├── hookparse_test.go │ │ └── preload.js ├── httpdump │ ├── http.go │ ├── http_test.go │ └── httpdump.go ├── mix │ ├── mixpayloads.go │ └── mixpayloads_test.go ├── notify │ ├── dingbot │ │ ├── dingbot.go │ │ └── dingbot_test.go │ ├── notify.go │ └── notify_test.go ├── parser │ ├── html │ │ ├── html.go │ │ ├── html_test.go │ │ ├── search.go │ │ └── search_test.go │ └── javascript │ │ ├── variable.go │ │ └── variable_test.go └── proxy │ ├── ca.go │ ├── cert │ ├── gen.go │ └── gen_test.go │ ├── mitm.go │ ├── util.go │ ├── util_test.go │ ├── xssfinder.ca.cert │ └── xssfinder.ca.key └── tests ├── demo ├── dom_eval.html └── dom_hookconvt.html └── notifier-temp.yaml /.github/workflows/release.md: -------------------------------------------------------------------------------- 1 | # 更新说明 2 | - 支持 window.Storage 来源检测 -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build And Release🎉 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | env: 12 | CGO_ENABLED: 0 13 | BUILD_FLAGS: -trimpath -ldflags "-w -s" -o 14 | steps: 15 | - name: Check out code 16 | uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v2 22 | with: 23 | go-version: 1.18 24 | 25 | - name: Make release dir 26 | run: mkdir release 27 | 28 | - name: Install Package 29 | run: | 30 | sudo apt install -y zip 31 | 32 | - name: Build linux release 33 | env: 34 | GOOS: linux 35 | run: | 36 | GOARCH=amd64 go build ${{ env.BUILD_FLAGS }} release/xssfinder_linux_amd64 ./cmd/xssfinder/xssfinder.go 37 | GOARCH=386 go build ${{ env.BUILD_FLAGS }} release/xssfinder_linux_386 ./cmd/xssfinder/xssfinder.go 38 | GOARCH=arm64 go build ${{ env.BUILD_FLAGS }} release/xssfinder_linux_arm64 ./cmd/xssfinder/xssfinder.go 39 | 40 | - name: Build windows release 41 | env: 42 | GOOS: windows 43 | run: | 44 | GOARCH=amd64 go build ${{ env.BUILD_FLAGS }} release/xssfinder_windows_amd64.exe ./cmd/xssfinder/xssfinder.go 45 | GOARCH=386 go build ${{ env.BUILD_FLAGS }} release/xssfinder_windows_386.exe ./cmd/xssfinder/xssfinder.go 46 | 47 | - name: Build darwin release 48 | env: 49 | GOOS: darwin 50 | run: | 51 | GOARCH=amd64 go build ${{ env.BUILD_FLAGS }} release/xssfinder_darwin_amd64 ./cmd/xssfinder/xssfinder.go 52 | GOARCH=arm64 go build ${{ env.BUILD_FLAGS }} release/xssfinder_darwin_arm64 ./cmd/xssfinder/xssfinder.go 53 | 54 | - name: Zip Release 55 | run: | 56 | cd release 57 | find . -name 'xssfinder_*' -type f -exec zip {}.zip {} \; -exec rm {} \; 58 | find . -name 'xssfinder_*.zip' -type f -exec shasum -a 256 {} \; > sha256.txt 59 | ls -alh 60 | 61 | - name: Upload release 62 | uses: softprops/action-gh-release@v1 63 | with: 64 | draft: true 65 | body_path: .github/workflows/release.md 66 | files: | 67 | release/xssfinder_linux_amd64.zip 68 | release/xssfinder_linux_386.zip 69 | release/xssfinder_linux_arm64.zip 70 | release/xssfinder_windows_amd64.exe.zip 71 | release/xssfinder_windows_386.exe.zip 72 | release/xssfinder_darwin_amd64.zip 73 | release/xssfinder_darwin_arm64.zip 74 | release/sha256.txt 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .env 17 | .vscode/ 18 | .DS_Store 19 | chrome/xss/dom/intercepted_test.go 20 | notifier.yaml 21 | # notes: 22 | xss.md 23 | xss.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Hyuga 3 |
4 | 5 | ## xssfinder 是什么? 6 | 7 | 基于 `chrome headless` 的 XSS 漏洞发现工具。 8 | 9 | 它的主要特性有: 10 | - 动态地语义分析网页中的`JavaScript`源码,Hook关键点,利用污点分析检出 Dom-Based XSS 11 | - 极低的误报率:通过监听浏览器对话框弹出事件等进行验证。 12 | - 启动模式:`被动代理`, (即将支持`主动爬虫扫描`)... 13 | - 漏洞通知:`dingbot`, ... 14 | ## 安装 15 | - Go Install 16 | 17 | `go install github.com/Buzz2d0/xssfinder/cmd/xssfinder@latest` 18 | 19 | - Github Release 20 | 21 | 前往 [releases](https://github.com/Buzz2d0/xssfinder/releases) 下载合适的版本然后从命令行运行即可。 22 | 23 | ## 用法 24 | 25 | ```bash 26 | $ ./xssfinder 27 | 28 | NAME: 29 | xssfinder - XSS discovery tool 30 | 31 | USAGE: 32 | xssfinder [global options] command [command options] [arguments...] 33 | 34 | VERSION: 35 | v0.1.0 36 | 37 | COMMANDS: 38 | mitm Passive agent scanning 39 | help, h Shows a list of commands or help for one command 40 | 41 | GLOBAL OPTIONS: 42 | --debug, -d enable debug mode (default: false) 43 | --verbose, --vv enable very-verbose mode (default: false) 44 | --notifier-yaml value set notifier yaml configuration file 45 | --outjson set logger output json format (default: false) 46 | --exec value, -e value set browser exec path 47 | --noheadless disable browser headless mode (default: false) 48 | --incognito enable browser incognito mode (default: false) 49 | --proxy value set proxy and all traffic will be routed from the proxy server through 50 | --help, -h show help (default: false) 51 | --version, -v print the version (default: false) 52 | ``` 53 | 54 | **使用示例:** 55 | 56 | - mitm 模式 57 | ```bash 58 | # 启动被动扫描(中间人)模式,默认监听 127.0.0.1:8222 59 | # 下载并信任证书 http://xssfinder.ca 60 | ./xssfinder mitm 61 | ``` 62 | ![](./docs/mitm-demo.png) 63 | 64 | - 漏洞通知 65 | 66 | [notifier.yaml](./tests/notifier-temp.yaml) 模版: 67 | 68 | ```yaml 69 | dingbot: 70 | token: xxx 71 | secret: xxxx 72 | ``` 73 | 74 | ```bash 75 | # --notifier-yaml 指定通知机器人配置 76 | ./xssfinder --notifier-yaml notifier.yaml mitm 77 | ``` 78 | ![](./docs/notify-demo.png) 79 | Hyuga 80 | 81 | ## Bypass headless detect 82 | 83 | ![](./docs/bypass-headless-detect.png) 84 | ## 规划 85 | 86 | - [x] 优化 cmd parse 87 | - [x] 优化 Runner & Worker 88 | - [ ] 支持检测反射XSS 89 | - [ ] 支持 Docker 一键部署 90 | - [ ] 主动爬虫扫描 91 | - [ ] Webhook 提交任务 92 | 93 | 94 | ## Thx 95 | 96 | - https://github.com/AsaiKen/dom-based-xss-finder 97 | - https://github.com/boy-hack 98 | - https://github.com/Qianlitp/crawlergo -------------------------------------------------------------------------------- /cmd/xssfinder/xssfinder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/Buzz2d0/xssfinder/internal/app" 7 | ) 8 | 9 | const version = "v0.1.2" 10 | 11 | func main() { 12 | a := app.New(version) 13 | if err := a.Run(os.Args); err != nil { 14 | panic(err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/bypass-headless-detect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ac0d3r/xssfinder/09797b54e587bf8606e87dbeb68b7dddf48fc6e0/docs/bypass-headless-detect.png -------------------------------------------------------------------------------- /docs/dingbot-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ac0d3r/xssfinder/09797b54e587bf8606e87dbeb68b7dddf48fc6e0/docs/dingbot-demo.png -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ac0d3r/xssfinder/09797b54e587bf8606e87dbeb68b7dddf48fc6e0/docs/logo.png -------------------------------------------------------------------------------- /docs/mitm-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ac0d3r/xssfinder/09797b54e587bf8606e87dbeb68b7dddf48fc6e0/docs/mitm-demo.png -------------------------------------------------------------------------------- /docs/notify-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ac0d3r/xssfinder/09797b54e587bf8606e87dbeb68b7dddf48fc6e0/docs/notify-demo.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Buzz2d0/xssfinder 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/chromedp/cdproto v0.0.0-20220725225757-5988d9195a6c 7 | github.com/chromedp/chromedp v0.8.3 8 | github.com/elazarl/goproxy v0.0.0-20220403042543-a53172b9392e 9 | github.com/gokitx/pkgs v0.0.0-20220427050926-6a5988096940 10 | github.com/sirupsen/logrus v1.9.0 11 | github.com/tdewolff/parse/v2 v2.5.29 12 | github.com/urfave/cli/v2 v2.11.1 13 | golang.org/x/net v0.0.0-20220412020605-290c469a71a5 14 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c 15 | gopkg.in/yaml.v2 v2.4.0 16 | ) 17 | 18 | require ( 19 | github.com/chromedp/sysutil v1.0.0 // indirect 20 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 21 | github.com/gobwas/httphead v0.1.0 // indirect 22 | github.com/gobwas/pool v0.2.1 // indirect 23 | github.com/gobwas/ws v1.1.0 // indirect 24 | github.com/josharian/intern v1.0.0 // indirect 25 | github.com/mailru/easyjson v0.7.7 // indirect 26 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 27 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 28 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chromedp/cdproto v0.0.0-20220725225757-5988d9195a6c h1:Gm+DujZPVAtQNTLhbg5PExjRNfhdTCSMLvJ/pFfY4aY= 2 | github.com/chromedp/cdproto v0.0.0-20220725225757-5988d9195a6c/go.mod h1:5Y4sD/eXpwrChIuxhSr/G20n9CdbCmoerOHnuAf0Zr0= 3 | github.com/chromedp/chromedp v0.8.3 h1:UwOY+fhC5Vv3uKgRpnvilCbWs/QPz8ciFwRB0q6pH8k= 4 | github.com/chromedp/chromedp v0.8.3/go.mod h1:9YfKSJnBNeP77vKecv+DNx2/Tcb+6Gli0d1aZPw/xbk= 5 | github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= 6 | github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 8 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/elazarl/goproxy v0.0.0-20220403042543-a53172b9392e h1:8dhROE/dIrz8nOJQjah6LG37QfL8fZhQTp1RDAjuNpQ= 13 | github.com/elazarl/goproxy v0.0.0-20220403042543-a53172b9392e/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= 14 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= 15 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 16 | github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= 17 | github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= 18 | github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= 19 | github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 20 | github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= 21 | github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= 22 | github.com/gokitx/pkgs v0.0.0-20220427050926-6a5988096940 h1:/nUAVWoaXHQaXPi3Y8xjhCE9HkKLysm3foPbIohbQpE= 23 | github.com/gokitx/pkgs v0.0.0-20220427050926-6a5988096940/go.mod h1:yDg2f6+a29XSwxiYGEk9IUK1rjUMeHaqEnT4dBMWGBs= 24 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 25 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 26 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 27 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 28 | github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 32 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 33 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 34 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 35 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 36 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 37 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 38 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 39 | github.com/tdewolff/parse/v2 v2.5.29 h1:Uf0OtZL9YaUXTuHEOitdo9lD90P0XTwCjZi+KbGChuM= 40 | github.com/tdewolff/parse/v2 v2.5.29/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho= 41 | github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4= 42 | github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= 43 | github.com/urfave/cli/v2 v2.11.1 h1:UKK6SP7fV3eKOefbS87iT9YHefv7iB/53ih6e+GNAsE= 44 | github.com/urfave/cli/v2 v2.11.1/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= 45 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 46 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 47 | golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4= 48 | golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 49 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 50 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 51 | golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 52 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= 53 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 54 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 55 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 56 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 57 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 58 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 59 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 60 | -------------------------------------------------------------------------------- /internal/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/Buzz2d0/xssfinder/internal/logger" 5 | "github.com/Buzz2d0/xssfinder/internal/options" 6 | "github.com/Buzz2d0/xssfinder/internal/runner" 7 | "github.com/urfave/cli/v2" 8 | ) 9 | 10 | func New(version string) *cli.App { 11 | app := &cli.App{ 12 | Name: "xssfinder", 13 | Version: version, 14 | Usage: "XSS discovery tool", 15 | Flags: []cli.Flag{ 16 | &cli.BoolFlag{ 17 | Name: "debug", 18 | Aliases: []string{"d"}, 19 | Value: false, 20 | Usage: "enable debug mode", 21 | }, 22 | &cli.BoolFlag{ 23 | Name: "verbose", 24 | Aliases: []string{"vv"}, 25 | Value: false, 26 | Usage: "enable very-verbose mode", 27 | }, 28 | &cli.StringFlag{ 29 | Name: "notifier-yaml", 30 | Value: "", 31 | Usage: "set notifier yaml configuration file", 32 | }, 33 | &cli.BoolFlag{ 34 | Name: "outjson", 35 | Value: false, 36 | Usage: "set logger output json format", 37 | }, 38 | &cli.StringFlag{ 39 | Name: "exec", 40 | Aliases: []string{"e"}, 41 | Value: "", 42 | Usage: "set browser exec path", 43 | }, 44 | &cli.BoolFlag{ 45 | Name: "noheadless", 46 | Value: false, 47 | Usage: "disable browser headless mode", 48 | }, 49 | &cli.BoolFlag{ 50 | Name: "incognito", 51 | Value: false, 52 | Usage: "enable browser incognito mode", 53 | }, 54 | &cli.StringFlag{ 55 | Name: "proxy", 56 | Value: "", 57 | Usage: "set proxy and all traffic will be routed from the proxy server through", 58 | }, 59 | }, 60 | Commands: []*cli.Command{ 61 | { 62 | Name: "mitm", 63 | Usage: "Passive agent scanning", 64 | Flags: []cli.Flag{ 65 | &cli.StringFlag{ 66 | Name: "addr", 67 | Value: "127.0.0.1:8222", 68 | Usage: "set mitm proxy server listen address", 69 | }, 70 | &cli.StringSliceFlag{ 71 | Name: "hosts", 72 | Value: cli.NewStringSlice(), 73 | Usage: "set mitm scan host whitelist", 74 | }, 75 | }, 76 | Action: func(c *cli.Context) error { 77 | return mitmAction(c) 78 | }, 79 | }, 80 | }, 81 | } 82 | 83 | return app 84 | } 85 | 86 | func mitmAction(c *cli.Context) error { 87 | opt := options.New().Set( 88 | options.WithDebug(c.Bool("debug")), 89 | options.WithVeryVerbose(c.Bool("verbose")), 90 | options.WithNotifierYaml(c.String("notifier-yaml")), 91 | options.WithLogOutJson(c.Bool("outjson")), 92 | options.WithBrowserExecPath(c.String("exec")), 93 | options.WithBrowserNoHeadless(c.Bool("noheadless")), 94 | options.WithBrowserIncognito(c.Bool("incognito")), 95 | options.WithBrowserProxy(c.String("proxy")), 96 | 97 | options.WithMitmVerbose(c.Bool("verbose")), 98 | options.WithMitmParentProxy(c.String("proxy")), 99 | options.WithMitmAddr(c.String("addr")), 100 | options.WithMitmTargetHosts(c.StringSlice("hosts")...), 101 | ) 102 | 103 | opt.Log.Level = opt.LogLevel() 104 | logger.Init(opt.Log) 105 | r, err := runner.NewRunner(opt) 106 | if err != nil { 107 | return err 108 | } 109 | r.Start() 110 | return nil 111 | } 112 | -------------------------------------------------------------------------------- /internal/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | const ( 10 | logTimeFormat = "2006-01-02 15:04:05" 11 | ) 12 | 13 | type Config struct { 14 | Level logrus.Level 15 | OutJson bool 16 | NoColor bool 17 | } 18 | 19 | func Init(c Config) { 20 | if c.OutJson { 21 | logrus.SetFormatter(&logrus.JSONFormatter{ 22 | TimestampFormat: logTimeFormat, 23 | }, 24 | ) 25 | } else { 26 | logrus.SetFormatter(&logrus.TextFormatter{ 27 | ForceColors: !c.NoColor, 28 | TimestampFormat: logTimeFormat, 29 | }, 30 | ) 31 | } 32 | 33 | logrus.SetLevel(c.Level) 34 | logrus.SetOutput(os.Stdout) 35 | 36 | if c.Level > logrus.DebugLevel { 37 | logrus.SetReportCaller(true) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/logger/logger_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func TestLogger(t *testing.T) { 10 | Init(Config{ 11 | Level: logrus.DebugLevel, 12 | NoColor: true, 13 | }) 14 | logrus.Debugln("tessss") 15 | } 16 | -------------------------------------------------------------------------------- /internal/options/options.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | import ( 4 | "github.com/Buzz2d0/xssfinder/internal/logger" 5 | "github.com/Buzz2d0/xssfinder/pkg/chrome/browser" 6 | "github.com/Buzz2d0/xssfinder/pkg/proxy" 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | type Options struct { 11 | Debug bool 12 | Verbose bool 13 | NotifierYaml string 14 | Mitm proxy.Config 15 | Log logger.Config 16 | Browser browser.Config 17 | } 18 | 19 | func (o *Options) LogLevel() logrus.Level { 20 | l := logrus.InfoLevel 21 | if o.Debug { 22 | l = logrus.DebugLevel 23 | } 24 | if o.Verbose { 25 | l = logrus.TraceLevel 26 | } 27 | return l 28 | } 29 | 30 | func New() *Options { 31 | return &Options{ 32 | Mitm: proxy.Config{ 33 | Addr: "127.0.0.1:8222", 34 | }, 35 | Log: logger.Config{ 36 | Level: logrus.InfoLevel, 37 | }, 38 | } 39 | } 40 | 41 | func (o *Options) Set(opts ...Option) *Options { 42 | for i := range opts { 43 | opts[i](o) 44 | } 45 | return o 46 | } 47 | 48 | type Option func(*Options) 49 | 50 | func WithDebug(debug bool) Option { 51 | return func(opt *Options) { 52 | opt.Debug = debug 53 | } 54 | } 55 | 56 | func WithVeryVerbose(verbose bool) Option { 57 | return func(opt *Options) { 58 | opt.Verbose = verbose 59 | } 60 | } 61 | 62 | func WithNotifierYaml(f string) Option { 63 | return func(opt *Options) { 64 | opt.NotifierYaml = f 65 | } 66 | } 67 | 68 | // log 69 | func WithLogOutJson(o bool) Option { 70 | return func(opt *Options) { 71 | opt.Log.OutJson = o 72 | } 73 | } 74 | 75 | // mitm 76 | func WithMitmAddr(addr string) Option { 77 | return func(opt *Options) { 78 | opt.Mitm.Addr = addr 79 | } 80 | } 81 | 82 | func WithMitmVerbose(verbose bool) Option { 83 | return func(opt *Options) { 84 | opt.Mitm.Verbose = verbose 85 | } 86 | } 87 | 88 | func WithMitmTargetHosts(hosts ...string) Option { 89 | return func(opt *Options) { 90 | if opt.Mitm.TargetHosts == nil { 91 | opt.Mitm.TargetHosts = make([]string, 0) 92 | } 93 | opt.Mitm.TargetHosts = append(opt.Mitm.TargetHosts, hosts...) 94 | } 95 | } 96 | 97 | func WithMitmParentProxy(p string) Option { 98 | return func(opt *Options) { 99 | opt.Mitm.ParentProxy = p 100 | } 101 | } 102 | 103 | // browser 104 | func WithBrowserExecPath(p string) Option { 105 | return func(opt *Options) { 106 | opt.Browser.ExecPath = p 107 | } 108 | } 109 | 110 | func WithBrowserNoHeadless(n bool) Option { 111 | return func(opt *Options) { 112 | opt.Browser.NoHeadless = n 113 | } 114 | } 115 | 116 | func WithBrowserIncognito(i bool) Option { 117 | return func(opt *Options) { 118 | opt.Browser.Incognito = i 119 | } 120 | } 121 | 122 | func WithBrowserProxy(p string) Option { 123 | return func(opt *Options) { 124 | opt.Browser.Proxy = p 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /internal/runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "sync" 8 | 9 | "github.com/Buzz2d0/xssfinder/internal/options" 10 | "github.com/Buzz2d0/xssfinder/pkg/chrome/browser" 11 | "github.com/Buzz2d0/xssfinder/pkg/notify" 12 | "github.com/Buzz2d0/xssfinder/pkg/proxy" 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | type Runner struct { 17 | opt *options.Options 18 | worker *Worker 19 | mitmServer *proxy.MitmServer 20 | } 21 | 22 | func NewRunner(opt *options.Options) (*Runner, error) { 23 | var ( 24 | notifier notify.Notifier 25 | err error 26 | ) 27 | if opt.NotifierYaml != "" { 28 | notifier, err = notify.NewNotifierWithYaml(opt.NotifierYaml) 29 | if err != nil { 30 | return nil, err 31 | } 32 | } 33 | browser := browser.NewBrowser(opt.Browser) 34 | mitm := proxy.NewMitmServer(opt.Mitm) 35 | worker := NewWorker(5e1, notifier, browser) 36 | 37 | r := &Runner{ 38 | opt: opt, 39 | mitmServer: mitm, 40 | worker: worker, 41 | } 42 | return r, nil 43 | } 44 | 45 | func (r *Runner) Start() { 46 | var ( 47 | err error 48 | once sync.Once 49 | ) 50 | ctx, cancel := context.WithCancel(context.Background()) 51 | 52 | var wg sync.WaitGroup 53 | e := make(chan os.Signal, 1) 54 | signal.Notify(e, os.Interrupt) 55 | 56 | wg.Add(1) 57 | go func() { 58 | defer wg.Done() 59 | if err := r.mitmServer.ListenAndServe(); err != nil { 60 | logrus.Error(err) 61 | once.Do(func() { close(e) }) 62 | } 63 | }() 64 | wg.Add(1) 65 | go func() { 66 | defer wg.Done() 67 | if err := r.worker.Start(ctx, r.mitmServer.C); err != nil { 68 | logrus.Error(err) 69 | once.Do(func() { close(e) }) 70 | } 71 | }() 72 | 73 | <-e 74 | cancel() 75 | if err = r.mitmServer.Shutdown(ctx); err != nil { 76 | logrus.Error(err) 77 | } 78 | r.worker.Wait() 79 | wg.Wait() 80 | } 81 | -------------------------------------------------------------------------------- /internal/runner/worker.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/Buzz2d0/xssfinder/pkg/chrome/browser" 11 | "github.com/Buzz2d0/xssfinder/pkg/chrome/bypass" 12 | "github.com/Buzz2d0/xssfinder/pkg/chrome/cookies" 13 | "github.com/Buzz2d0/xssfinder/pkg/chrome/xss/checker" 14 | "github.com/Buzz2d0/xssfinder/pkg/chrome/xss/dom" 15 | "github.com/Buzz2d0/xssfinder/pkg/httpdump" 16 | "github.com/Buzz2d0/xssfinder/pkg/notify" 17 | "github.com/chromedp/chromedp" 18 | "github.com/gokitx/pkgs/limiter" 19 | "github.com/sirupsen/logrus" 20 | ) 21 | 22 | type Worker struct { 23 | *limiter.Limiter 24 | 25 | notifier notify.Notifier 26 | browser *browser.Browser 27 | preActions []chromedp.Action 28 | } 29 | 30 | func NewWorker(limitNum int64, 31 | notifier notify.Notifier, 32 | browser *browser.Browser) *Worker { 33 | return &Worker{ 34 | Limiter: limiter.New(limitNum), 35 | notifier: notifier, 36 | browser: browser, 37 | preActions: []chromedp.Action{bypass.BypassHeadlessDetect()}, 38 | } 39 | } 40 | 41 | func (w *Worker) Start(ctx context.Context, C <-chan httpdump.Request) error { 42 | ctx, cancel := context.WithCancel(ctx) 43 | defer cancel() 44 | 45 | if err := w.browser.Start(ctx); err != nil { 46 | return err 47 | } 48 | defer w.browser.Close() 49 | 50 | for { 51 | select { 52 | case <-ctx.Done(): 53 | return ctx.Err() 54 | case task := <-C: 55 | w.Allow() 56 | logrus.Infoln("[worker] received task:", task.URL, task.Response.Status) 57 | go func(ctx context.Context, req httpdump.Request) { 58 | defer w.Done() 59 | if err := w.scan(ctx, req); err != nil { 60 | logrus.Errorln("[worker] scan task error:", err) 61 | } 62 | }(ctx, task) 63 | } 64 | } 65 | } 66 | 67 | func (w *Worker) scan(ctx context.Context, req httpdump.Request) error { 68 | var preTasks chromedp.Tasks 69 | preTasks = w.preActions[:] 70 | if len(req.Cookies) != 0 { 71 | logrus.Debugf("[worker] cookies: %#v \n", req.Cookies) 72 | preTasks = append(preTasks, cookies.SetWithHttpCookie(req.Cookies)) 73 | } 74 | 75 | switch req.Method { 76 | case http.MethodGet: 77 | // TODO only detecting dom-based XSS 78 | vuls := make([]dom.VulPoint, 0) 79 | if err := w.browser.Scan(dom.GenTasks(req.URL, &vuls, time.Second*8, preTasks)); err != nil { 80 | return err 81 | } 82 | if len(vuls) != 0 { 83 | logrus.Info("[worker] dom-based scan vuls:") 84 | for _, vulPoint := range vuls { 85 | logrus.Infof("\t url: %s source: %s sink: %s \n", vulPoint.Url, vulPoint.Source.Label, vulPoint.Sink.Label) 86 | } 87 | // TODO 根据 source 构造 payload 88 | // w.checkDomPoc(ctx, vuls, preTasks) 89 | } 90 | } 91 | return nil 92 | } 93 | 94 | func (w *Worker) checkDomPoc(ctx context.Context, points []dom.VulPoint, preActions chromedp.Tasks) { 95 | var res bool 96 | for _, point := range points { 97 | pocUrls, err := dom.GenPocUrls(point) 98 | if err != nil { 99 | logrus.Errorln("[worker] gen dom poc urls error:", err) 100 | continue 101 | } 102 | 103 | for _, poc := range pocUrls { 104 | res = false 105 | if err := w.browser.Scan(checker.GenTasks(poc.Url, 106 | poc.Rand, 107 | &res, 108 | time.Second*5, 109 | preActions)); err != nil { 110 | logrus.Errorln("[worker] check dom poc url error:", err) 111 | } 112 | 113 | if res { 114 | w.reportDom(poc.Url, point) 115 | } 116 | } 117 | } 118 | } 119 | 120 | func (w *Worker) reportDom(url string, point dom.VulPoint) { 121 | // TODO report 122 | p, _ := json.MarshalIndent(point, "", " ") 123 | logrus.Infof("[report] url: %s\n\ttype: dom-based\n\tdesc: %s\n", url, string(p)) 124 | if w.notifier != nil { 125 | w.notifier.Notify(url, 126 | fmt.Sprintf("- type dom-based\n- desc \n\n ```\n%s\n```", string(p)), 127 | ) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /pkg/chrome/browser/browser.go: -------------------------------------------------------------------------------- 1 | package browser 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/url" 7 | "sync" 8 | "sync/atomic" 9 | 10 | "github.com/chromedp/cdproto/browser" 11 | "github.com/chromedp/chromedp" 12 | "github.com/sirupsen/logrus" 13 | ) 14 | 15 | const ( 16 | userAgent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.0 Safari/537.36" 17 | ) 18 | 19 | type Config struct { 20 | ExecPath string 21 | NoHeadless bool 22 | Incognito bool 23 | Proxy string 24 | } 25 | 26 | type Browser struct { 27 | once sync.Once 28 | mux sync.RWMutex 29 | 30 | opts []chromedp.ExecAllocatorOption 31 | ctx *context.Context 32 | cancelA, cancelC context.CancelFunc 33 | running int64 34 | } 35 | 36 | func NewBrowser(c Config) *Browser { 37 | opts := append(chromedp.DefaultExecAllocatorOptions[:], 38 | chromedp.Flag("headless", !c.NoHeadless), // 无头模式 39 | chromedp.Flag("disable-gpu", true), // 禁用GPU,不显示GUI 40 | chromedp.Flag("incognito", c.Incognito), // 隐身模式启动 41 | chromedp.Flag("no-sandbox", true), // 取消沙盒模式 42 | chromedp.Flag("ignore-certificate-errors", true), // 忽略证书错误 43 | chromedp.Flag("disable-images", true), 44 | chromedp.Flag("disable-web-security", true), 45 | chromedp.Flag("disable-xss-auditor", true), 46 | chromedp.Flag("disable-setuid-sandbox", true), 47 | chromedp.Flag("allow-running-insecure-content", true), 48 | chromedp.Flag("disable-webgl", true), 49 | chromedp.Flag("disable-popup-blocking", true), 50 | chromedp.UserAgent(userAgent), 51 | chromedp.WindowSize(1920, 1080), 52 | ) 53 | if c.Proxy != "" { // 设置浏览器代理 54 | if _, err := url.Parse(c.Proxy); err == nil { 55 | opts = append(opts, chromedp.ProxyServer(c.Proxy)) 56 | } 57 | } 58 | if c.ExecPath != "" { // 设置浏览器执行路径 59 | opts = append(opts, chromedp.ExecPath(c.ExecPath)) 60 | } 61 | 62 | browser := &Browser{ 63 | opts: opts, 64 | } 65 | return browser 66 | } 67 | 68 | func (b *Browser) Start(ctx context.Context) error { 69 | var err error 70 | b.once.Do(func() { 71 | err = b.init(ctx) 72 | }) 73 | return err 74 | } 75 | 76 | func (b *Browser) init(ctx context.Context) error { 77 | b.mux.Lock() 78 | defer b.mux.Unlock() 79 | 80 | ctx, cancelA := chromedp.NewExecAllocator(ctx, b.opts...) 81 | bctx, cancelC := chromedp.NewContext(ctx) 82 | 83 | if err := chromedp.Run(bctx); err != nil { 84 | return err 85 | } 86 | b.ctx = &bctx 87 | b.cancelA = cancelA 88 | b.cancelC = cancelC 89 | return nil 90 | } 91 | 92 | func (b *Browser) Scan(tasks chromedp.Tasks) error { 93 | b.mux.RLock() 94 | defer b.mux.RUnlock() 95 | 96 | if b.ctx == nil || b.cancelA == nil || b.cancelC == nil { 97 | return fmt.Errorf("browser engine not started") 98 | } 99 | 100 | atomic.AddInt64(&b.running, 1) 101 | defer atomic.AddInt64(&b.running, -1) 102 | 103 | ctx, cancel := chromedp.NewContext(*b.ctx) 104 | defer cancel() 105 | 106 | return chromedp.Run(ctx, tasks) 107 | } 108 | 109 | func (b *Browser) Running() int64 { 110 | return b.running 111 | } 112 | 113 | func (b *Browser) Close() { 114 | logrus.Debugln("browser close") 115 | 116 | b.mux.Lock() 117 | defer b.mux.Unlock() 118 | if b.ctx == nil || b.cancelA == nil || b.cancelC == nil { 119 | return 120 | } 121 | 122 | // close main browser 123 | b.cancelC() 124 | b.cancelA() 125 | if err := browser.Close().Do(*b.ctx); err != nil { 126 | logrus.Errorln("browser page close error:", err) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /pkg/chrome/browser/browser_test.go: -------------------------------------------------------------------------------- 1 | package browser 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "testing" 8 | "time" 9 | 10 | "github.com/chromedp/cdproto/browser" 11 | "github.com/chromedp/chromedp" 12 | "github.com/sirupsen/logrus" 13 | ) 14 | 15 | func TestMain(m *testing.M) { 16 | logrus.SetLevel(logrus.TraceLevel) 17 | m.Run() 18 | } 19 | 20 | func TestBrowser(t *testing.T) { 21 | browser := NewBrowser(Config{ 22 | NoHeadless: true, 23 | }) 24 | 25 | t.Log(browser.Start(context.Background())) 26 | 27 | for i := 0; i < 3; i++ { 28 | t.Log(browser.Scan(chromedp.Tasks{chromedp.Navigate("https://www.baidu.com")})) 29 | time.Sleep(5 * time.Second) 30 | } 31 | browser.Close() 32 | } 33 | 34 | func TestChromedpTabs(t *testing.T) { 35 | // https://github.com/chromedp/chromedp/issues/824 36 | opts := append(chromedp.DefaultExecAllocatorOptions[:], 37 | chromedp.Flag("headless", false), 38 | ) 39 | ctx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) 40 | defer cancel() 41 | 42 | bctx, cancel := chromedp.NewContext(ctx) // the next line does not have any effect because there are not browsers created by this context. 43 | 44 | // call Run on browser context to start a browser first 45 | chromedp.Run(bctx) 46 | defer cancel() 47 | 48 | for i := 0; i < 3; i++ { 49 | tctx, cancel := chromedp.NewContext(bctx) 50 | // since the parent context has browser allocated, 51 | // the Run will create new tab in the browser. 52 | chromedp.Run(tctx, chromedp.Navigate("https://www.baidu.com")) 53 | if err := browser.Close().Do(tctx); err != nil { 54 | log.Println(err) 55 | } 56 | cancel() 57 | time.Sleep(2 * time.Second) 58 | } 59 | 60 | if err := browser.Close().Do(bctx); err != nil { 61 | log.Println(err) 62 | } 63 | } 64 | 65 | func TestContext(t *testing.T) { 66 | ctx, cancel := context.WithCancel(context.Background()) 67 | defer cancel() 68 | 69 | go func() { 70 | <-ctx.Done() 71 | fmt.Println("123") 72 | }() 73 | 74 | time.Sleep(time.Second) 75 | fmt.Println("xxx") 76 | } 77 | -------------------------------------------------------------------------------- /pkg/chrome/bypass/bypass.go: -------------------------------------------------------------------------------- 1 | package bypass 2 | 3 | import ( 4 | "context" 5 | _ "embed" 6 | 7 | "github.com/chromedp/cdproto/page" 8 | "github.com/chromedp/chromedp" 9 | ) 10 | 11 | //go:embed stealth.min.js 12 | var stealthJS string 13 | 14 | func BypassHeadlessDetect() chromedp.Action { 15 | return chromedp.ActionFunc(func(ctx context.Context) error { 16 | _, err := page.AddScriptToEvaluateOnNewDocument(stealthJS).Do(ctx) 17 | return err 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /pkg/chrome/bypass/bypass_test.go: -------------------------------------------------------------------------------- 1 | package bypass 2 | 3 | import ( 4 | "context" 5 | "io/ioutil" 6 | "log" 7 | "testing" 8 | 9 | "github.com/chromedp/chromedp" 10 | ) 11 | 12 | func TestByass(t *testing.T) { 13 | buf, err := screenshot("https://bot.sannysoft.com/") 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | 18 | err = saveToFile(buf, "BypassHeadlessDetect.png") 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | } 23 | 24 | func screenshot(url string) ([]byte, error) { 25 | opts := append(chromedp.DefaultExecAllocatorOptions[:], 26 | chromedp.Flag("ignore-certificate-errors", true), 27 | chromedp.UserAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.0 Safari/537.36"), 28 | ) 29 | var ( 30 | ctx context.Context 31 | cancelA, cancelC context.CancelFunc 32 | ) 33 | ctx, cancelA = chromedp.NewExecAllocator(context.Background(), opts...) 34 | defer cancelA() 35 | 36 | ctx, cancelC = chromedp.NewContext(ctx) 37 | defer cancelC() 38 | 39 | defer func() { 40 | if err := chromedp.Cancel(ctx); err != nil { 41 | log.Printf("[Screenshot] Cancel chrome error: %v", err) 42 | } 43 | }() 44 | 45 | var buf []byte 46 | if err := chromedp.Run(ctx, chromedp.Tasks{ 47 | BypassHeadlessDetect(), 48 | chromedp.Navigate(url), 49 | chromedp.CaptureScreenshot(&buf), 50 | }); err != nil { 51 | return nil, err 52 | } 53 | return buf, nil 54 | } 55 | 56 | func saveToFile(buf []byte, file string) error { 57 | if err := ioutil.WriteFile(file, buf, 0644); err != nil { 58 | return err 59 | } 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/chrome/cookies/cookies.go: -------------------------------------------------------------------------------- 1 | package cookies 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/Buzz2d0/xssfinder/pkg/httpdump" 7 | "github.com/chromedp/cdproto/cdp" 8 | "github.com/chromedp/cdproto/network" 9 | "github.com/chromedp/chromedp" 10 | ) 11 | 12 | func SetWithHttpCookie(c []httpdump.Cookie) chromedp.Action { 13 | cookies := make([]*network.CookieParam, len(c)) 14 | for i := range c { 15 | 16 | expr := cdp.TimeSinceEpoch(c[i].Expires) 17 | cookies[i] = &network.CookieParam{ 18 | Name: c[i].Name, 19 | Value: c[i].Value, 20 | Domain: c[i].Domain, 21 | Path: c[i].Path, 22 | HTTPOnly: c[i].HttpOnly, 23 | Secure: c[i].Secure, 24 | Expires: &expr, 25 | } 26 | } 27 | return chromedp.ActionFunc(func(ctx context.Context) error { 28 | return network.SetCookies(cookies).Do(ctx) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/chrome/xss/checker/checker.go: -------------------------------------------------------------------------------- 1 | package checker 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/chromedp/cdproto/network" 10 | "github.com/chromedp/cdproto/page" 11 | "github.com/chromedp/chromedp" 12 | "github.com/sirupsen/logrus" 13 | ) 14 | 15 | func GenTasks(url, kerword string, res *bool, timeout time.Duration, before ...chromedp.Action) chromedp.Tasks { 16 | return chromedp.Tasks{ 17 | network.Enable(), 18 | chromedp.Tasks(before), 19 | chromedp.ActionFunc(func(ctx context.Context) error { 20 | var ( 21 | fired = make(chan struct{}) 22 | captured = make(chan struct{}) 23 | ) 24 | lctx, cancel := context.WithCancel(ctx) 25 | defer cancel() 26 | 27 | chromedp.ListenTarget(lctx, func(ev interface{}) { 28 | switch e := ev.(type) { 29 | case *page.EventJavascriptDialogOpening: 30 | logrus.Traceln("[CheckPoc]", e) 31 | if strings.Contains(e.Message, kerword) { 32 | *res = true 33 | captured <- struct{}{} 34 | } 35 | case *page.EventLoadEventFired: 36 | fired <- struct{}{} 37 | } 38 | }) 39 | 40 | var err error 41 | go func(e *error) { 42 | _, _, errorText, err := page.Navigate(url).Do(ctx) 43 | if err != nil { 44 | *e = err 45 | } 46 | if errorText != "" { 47 | *e = fmt.Errorf("page load error %s", errorText) 48 | } 49 | }(&err) 50 | 51 | select { 52 | case <-captured: 53 | // 如果成功触发对话框,页面处于 loadding 状态 54 | if err := page.StopLoading().Do(ctx); err != nil { 55 | return err 56 | } 57 | if err := page.HandleJavaScriptDialog(true).Do(ctx); err != nil { 58 | return err 59 | } 60 | logrus.Traceln("[CheckPoc] captured") 61 | case <-time.After(timeout): 62 | if err := page.StopLoading().Do(ctx); err != nil { 63 | return err 64 | } 65 | logrus.Traceln("[CheckPoc] timeout") 66 | case <-fired: 67 | logrus.Traceln("[CheckPoc] fired") 68 | } 69 | return err 70 | }), 71 | page.Close(), 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pkg/chrome/xss/checker/checker_test.go: -------------------------------------------------------------------------------- 1 | package checker 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/chromedp/chromedp" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | func TestMain(m *testing.M) { 13 | logrus.SetLevel(logrus.TraceLevel) 14 | m.Run() 15 | } 16 | 17 | func TestChecker(t *testing.T) { 18 | url := "http://localhost:8080/dom_test.html#12323%27%22%3E%3Cimg%20src=x%20onerror=alert(42689)%3E" 19 | 20 | opts := append(chromedp.DefaultExecAllocatorOptions[:], 21 | chromedp.Flag("headless", true), 22 | ) 23 | var ( 24 | cancelA context.CancelFunc 25 | ctx context.Context 26 | ) 27 | ctx, cancelA = chromedp.NewExecAllocator(context.Background(), opts...) 28 | defer cancelA() 29 | 30 | var cancelC context.CancelFunc 31 | ctx, cancelC = chromedp.NewContext(ctx) 32 | defer cancelC() 33 | 34 | defer func() { 35 | if err := chromedp.Cancel(ctx); err != nil { 36 | t.Log(err) 37 | } 38 | }() 39 | 40 | var res bool 41 | if err := chromedp.Run(ctx, GenTasks(url, "42689", &res, time.Second*12)); err != nil { 42 | t.Log(err) 43 | } 44 | 45 | t.Log(res) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/chrome/xss/dom/dom.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | _ "embed" 7 | "encoding/base64" 8 | "encoding/json" 9 | "fmt" 10 | "regexp" 11 | "sync" 12 | "time" 13 | 14 | "github.com/chromedp/cdproto/fetch" 15 | "github.com/chromedp/cdproto/network" 16 | "github.com/chromedp/cdproto/page" 17 | "github.com/chromedp/cdproto/runtime" 18 | "github.com/chromedp/chromedp" 19 | "github.com/gokitx/pkgs/bytesconv" 20 | "github.com/sirupsen/logrus" 21 | ) 22 | 23 | const ( 24 | eventPushVul = "xssfinderPushDomVul" 25 | ) 26 | 27 | var ( 28 | //go:embed preload.js 29 | preloadJS string 30 | ) 31 | 32 | func GenTasks(url string, vuls *[]VulPoint, timeout time.Duration, before ...chromedp.Action) chromedp.Tasks { 33 | return chromedp.Tasks{ 34 | runtime.Enable(), 35 | network.Enable(), 36 | // 开启响应拦截 37 | fetch.Enable().WithPatterns([]*fetch.RequestPattern{ 38 | { 39 | URLPattern: "*", 40 | ResourceType: network.ResourceTypeDocument, 41 | RequestStage: fetch.RequestStageResponse, 42 | }, 43 | { 44 | URLPattern: "*", 45 | ResourceType: network.ResourceTypeScript, 46 | RequestStage: fetch.RequestStageResponse, 47 | }, 48 | }), 49 | runtime.AddBinding(eventPushVul), 50 | chromedp.Tasks(before), 51 | loadDomPreloadJavaScript(), 52 | chromedp.ActionFunc(func(ctx context.Context) error { 53 | var ( 54 | once sync.Once 55 | mux sync.Mutex 56 | wg sync.WaitGroup 57 | fired = make(chan struct{}) 58 | ) 59 | lctx, cancel := context.WithCancel(ctx) 60 | defer cancel() 61 | 62 | chromedp.ListenTarget(lctx, func(ev interface{}) { 63 | switch e := ev.(type) { 64 | case *fetch.EventRequestPaused: 65 | logrus.Debugf("[dom-based] EventRequestPaused: %s %d\n", e.Request.URL, e.ResponseStatusCode) 66 | if e.ResponseStatusCode == 0 { 67 | return 68 | } 69 | wg.Add(1) 70 | go func() { // convert javascript 71 | defer wg.Done() 72 | if err := parseDomHooktResponseJs(lctx, e); err != nil { 73 | logrus.Errorf("[dom-based] hook %s error: %s\n", e.Request.URL, err) 74 | } 75 | }() 76 | case *runtime.EventBindingCalled: 77 | switch e.Name { 78 | case eventPushVul: 79 | logrus.Traceln("[dom-based] EventBindingCalled", e.Payload) 80 | 81 | points := make([]VulPoint, 0) 82 | if err := json.Unmarshal([]byte(e.Payload), &points); err != nil { 83 | logrus.Errorln("[dom-based] json.Unmarshal error:", err) 84 | return 85 | } 86 | mux.Lock() 87 | *vuls = append(*vuls, points...) 88 | mux.Unlock() 89 | } 90 | case *page.EventLoadEventFired: 91 | logrus.Traceln("[dom-based] EventLoadEventFired") 92 | once.Do(func() { close(fired) }) 93 | } 94 | }) 95 | 96 | var err error 97 | go func(e *error) { 98 | _, _, errorText, err := page.Navigate(url).Do(ctx) 99 | if err != nil { 100 | *e = err 101 | } 102 | if errorText != "" { 103 | *e = fmt.Errorf("page load error %s", errorText) 104 | } 105 | }(&err) 106 | 107 | select { 108 | case <-time.After(timeout): 109 | if err := page.StopLoading().Do(ctx); err != nil { 110 | return err 111 | } 112 | case <-fired: 113 | } 114 | wg.Wait() 115 | return err 116 | }), 117 | page.Close(), 118 | } 119 | } 120 | 121 | func loadDomPreloadJavaScript() chromedp.Action { 122 | return chromedp.ActionFunc( 123 | func(ctx context.Context) error { 124 | _, err := page.AddScriptToEvaluateOnNewDocument(preloadJS).Do(ctx) 125 | return err 126 | }) 127 | } 128 | 129 | var ( 130 | scriptContentRex = regexp.MustCompile(`]*?>(?:\s*\s*)?<\/script>`) 131 | ) 132 | 133 | func parseDomHooktResponseJs(ctx context.Context, event *fetch.EventRequestPaused) error { 134 | resBody, err := fetch.GetResponseBody(event.RequestID).Do(ctx) 135 | if err != nil { 136 | return err 137 | } 138 | 139 | switch event.ResourceType { 140 | case network.ResourceTypeDocument: 141 | ss := scriptContentRex.FindAllSubmatch(resBody, -1) 142 | for i := range ss { 143 | convedBody, err := HookParse(bytesconv.BytesToString(ss[i][1])) 144 | if err != nil { 145 | logrus.Errorf("[dom-based] hookconv %s error: %s\n", event.Request.URL, err) 146 | continue 147 | } 148 | resBody = bytes.Replace(resBody, ss[i][1], bytesconv.StringToBytes(convedBody), 1) 149 | } 150 | return fetch.FulfillRequest(event.RequestID, event.ResponseStatusCode).WithBody(base64.StdEncoding.EncodeToString(resBody)).Do(ctx) 151 | case network.ResourceTypeScript: 152 | convertedResBody, err := HookParse(bytesconv.BytesToString(resBody)) 153 | if err != nil { 154 | return err 155 | } 156 | return fetch.FulfillRequest(event.RequestID, event.ResponseStatusCode).WithBody(base64.StdEncoding.EncodeToString( 157 | bytesconv.StringToBytes(convertedResBody))).Do(ctx) 158 | } 159 | return nil 160 | } 161 | -------------------------------------------------------------------------------- /pkg/chrome/xss/dom/dom_test.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/chromedp/chromedp" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | func TestMain(m *testing.M) { 13 | logrus.SetLevel(logrus.FatalLevel) 14 | m.Run() 15 | } 16 | 17 | func TestDom(t *testing.T) { 18 | url := "https://public-firing-range.appspot.com/dom/toxicdom/localStorage/function/eval" 19 | opts := append(chromedp.DefaultExecAllocatorOptions[:], 20 | chromedp.Flag("headless", false), 21 | chromedp.ProxyServer("http://127.0.0.1:7890"), 22 | ) 23 | 24 | var ( 25 | cancelA context.CancelFunc 26 | ctx context.Context 27 | ) 28 | ctx, cancelA = chromedp.NewExecAllocator(context.Background(), opts...) 29 | defer cancelA() 30 | 31 | var cancelC context.CancelFunc 32 | ctx, cancelC = chromedp.NewContext(ctx) 33 | defer cancelC() 34 | 35 | defer func() { 36 | if err := chromedp.Cancel(ctx); err != nil { 37 | t.Log(err) 38 | } 39 | }() 40 | 41 | vuls := make([]VulPoint, 0) 42 | if err := chromedp.Run(ctx, GenTasks(url, &vuls, time.Minute*1)); err != nil { 43 | t.Log(err) 44 | } 45 | 46 | t.Log(vuls) 47 | } 48 | 49 | func TestRex(t *testing.T) { 50 | ss := scriptContentRex.FindAllStringSubmatch(` 53 | `, -1) 56 | // fmt.Printf("%#v \n", ss) 57 | for i := range ss { 58 | for j := range ss[i] { 59 | t.Log(ss[i][j]) 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /pkg/chrome/xss/dom/fuzz.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "net/url" 5 | "strings" 6 | 7 | "github.com/gokitx/pkgs/random" 8 | 9 | "github.com/Buzz2d0/xssfinder/pkg/mix" 10 | ) 11 | 12 | type VulPoint struct { 13 | Url string `json:"url"` 14 | Source TrackChain `json:"source"` 15 | Sink TrackChain `json:"sink"` 16 | } 17 | 18 | type TrackChain struct { 19 | Label string `json:"label"` 20 | Stacktrace []struct { 21 | Url string `json:"url"` 22 | Line string `json:"line"` 23 | Column string `json:"column"` 24 | } `json:"stacktrace"` 25 | } 26 | 27 | var ( 28 | fuzzPrefixes = []string{ 29 | `javascript://alert({{RAND}})//`, 30 | } 31 | fuzzSuffixes = []string{ 32 | `'-alert({{RAND}})-'`, 33 | `"-alert({{RAND}})-"`, 34 | `-alert({{RAND}})-`, 35 | `'">`, 36 | `alert({{RAND}})`, 37 | } 38 | ) 39 | 40 | func genRand(s string) (string, string) { 41 | r := random.RandomDigitString(5) 42 | return strings.ReplaceAll(s, "{{RAND}}", r), r 43 | } 44 | 45 | type FuzzUrl struct { 46 | Url string `json:"url"` 47 | Rand string `json:"rand"` 48 | } 49 | 50 | // GenPocUrls generates fuzz urls with payload 51 | func GenPocUrls(point VulPoint) ([]FuzzUrl, error) { 52 | payloads := make([]FuzzUrl, 0) 53 | 54 | u, err := url.Parse(point.Url) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | prefixURLs := mix.Payloads(*u, fuzzPrefixes, []mix.Rule{mix.RuleAppendPrefix, mix.RuleReplace}, mix.DefaultScopes) 60 | for _, u := range prefixURLs { 61 | furl, rand := genRand(u.String()) 62 | payloads = append(payloads, FuzzUrl{Url: furl, Rand: rand}) 63 | } 64 | 65 | suffixURLs := mix.Payloads(*u, fuzzSuffixes, []mix.Rule{mix.RuleAppendSuffix, mix.RuleReplace}, mix.DefaultScopes) 66 | for _, u := range suffixURLs { 67 | furl, rand := genRand(u.String()) 68 | payloads = append(payloads, FuzzUrl{Url: furl, Rand: rand}) 69 | } 70 | 71 | // TODO referrer 72 | // if strings.Contains(point.Source.Label, "referrer") { 73 | // for _, suf := range fuzzSuffixes { 74 | // u, rand := genRand(fmt.Sprintf("%s&%s", point.Url, suf)) 75 | // payloads = append(payloads, FuzzUrl{ 76 | // Url: u, 77 | // Rand: rand, 78 | // }) 79 | // } 80 | // } 81 | 82 | return payloads, nil 83 | } 84 | -------------------------------------------------------------------------------- /pkg/chrome/xss/dom/fuzz_test.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "testing" 7 | ) 8 | 9 | func TestURL(t *testing.T) { 10 | u, _ := url.Parse("http://localhost:8080/dom_test.html#12323") 11 | t.Logf("%#v", u) 12 | 13 | u.Fragment = "abc" + "12" 14 | t.Log(u.String()) 15 | 16 | u, _ = url.Parse("https://hyuga.icu?a=1&b=2") 17 | t.Logf("%#v %#v", u, u.Query()) 18 | q2 := u.Query() 19 | q2.Set("a", "2333") 20 | u.RawQuery = q2.Encode() 21 | 22 | t.Log(u.String()) 23 | } 24 | 25 | func TestGenPocUrls(t *testing.T) { 26 | urls, _ := GenPocUrls(VulPoint{ 27 | Url: "https://hyuga.icu?a=1&b=2#cc", 28 | // Url: "http://localhost:8080/dom_test.html#12323", 29 | }) 30 | logurls(urls) 31 | 32 | t.Log(fuzzPrefixes) 33 | t.Log(fuzzSuffixes) 34 | } 35 | 36 | func logurls(urls []FuzzUrl) { 37 | for _, f := range urls { 38 | fmt.Printf("%s - %s\n\n", f.Url, f.Rand) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pkg/chrome/xss/dom/hookparse.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/tdewolff/parse/v2" 7 | "github.com/tdewolff/parse/v2/js" 8 | ) 9 | 10 | func HookParse(code string) (string, error) { 11 | ast, err := js.Parse(parse.NewInputString(code), js.Options{}) 12 | if err != nil { 13 | return "", err 14 | } 15 | convWalk(ast) 16 | 17 | return ast.JS(), nil 18 | } 19 | 20 | func convExpr(node js.IExpr) js.IExpr { 21 | switch n := node.(type) { 22 | case *js.BinaryExpr: 23 | switch n.Op { 24 | case js.AddToken: // + 25 | return &js.CallExpr{ 26 | X: &js.Var{Data: []byte("__xssfinder_plus")}, 27 | Args: js.Args{ 28 | List: []js.Arg{{Value: convExpr(n.X)}, {Value: convExpr(n.Y)}}, 29 | }, 30 | } 31 | case js.AddEqToken: // += 32 | // // a += b ---> a = __xssfinder_plus(a, b) 33 | n.Op = js.EqToken 34 | n.Y = &js.CallExpr{ 35 | X: &js.Var{Data: []byte("__xssfinder_plus")}, 36 | Args: js.Args{ 37 | List: []js.Arg{{Value: convExpr(n.X)}, {Value: convExpr(n.Y)}}, 38 | }, 39 | } 40 | case js.EqEqToken: // == 41 | return &js.CallExpr{ 42 | X: &js.Var{Data: []byte("__xssfinder_equal")}, 43 | Args: js.Args{ 44 | List: []js.Arg{{Value: convExpr(n.X)}, {Value: convExpr(n.Y)}}, 45 | }, 46 | } 47 | case js.NotEqToken: // != 48 | return &js.CallExpr{ 49 | X: &js.Var{Data: []byte("__xssfinder_notEqual")}, 50 | Args: js.Args{ 51 | List: []js.Arg{{Value: convExpr(n.X)}, {Value: convExpr(n.Y)}}, 52 | }, 53 | } 54 | case js.EqEqEqToken: // === 55 | return &js.CallExpr{ 56 | X: &js.Var{Data: []byte("__xssfinder_strictEqual")}, 57 | Args: js.Args{ 58 | List: []js.Arg{{Value: convExpr(n.X)}, {Value: convExpr(n.Y)}}, 59 | }, 60 | } 61 | case js.NotEqEqToken: // !== 62 | return &js.CallExpr{ 63 | X: &js.Var{Data: []byte("__xssfinder_strictNotEqual")}, 64 | Args: js.Args{ 65 | List: []js.Arg{{Value: convExpr(n.X)}, {Value: convExpr(n.Y)}}, 66 | }, 67 | } 68 | case js.EqToken: // = 69 | if obj, ok := n.X.(*js.DotExpr); ok { 70 | return &js.CallExpr{ 71 | X: &js.Var{Data: []byte("__xssfinder_put")}, 72 | Args: js.Args{ 73 | List: []js.Arg{ 74 | {Value: obj.X}, 75 | {Value: &js.LiteralExpr{ 76 | TokenType: js.StringToken, 77 | Data: []byte(fmt.Sprintf(`"%s"`, obj.Y.JS())), 78 | }}, 79 | {Value: convExpr(n.Y)}}, 80 | }, 81 | } 82 | } else { 83 | n.Y = convExpr(n.Y) 84 | } 85 | } 86 | case *js.DotExpr: 87 | return &js.CallExpr{ 88 | X: &js.Var{Data: []byte("__xssfinder_get")}, 89 | Args: js.Args{ 90 | List: []js.Arg{{Value: convExpr(n.X)}, {Value: &js.LiteralExpr{ 91 | TokenType: js.StringToken, 92 | Data: []byte(fmt.Sprintf(`"%s"`, n.Y.JS())), 93 | }}}, 94 | }, 95 | } 96 | case *js.IndexExpr: 97 | return &js.CallExpr{ 98 | X: &js.Var{Data: []byte("__xssfinder_get")}, 99 | Args: js.Args{ 100 | List: []js.Arg{{Value: convExpr(n.X)}, {Value: convExpr(n.Y)}}, 101 | }, 102 | } 103 | case *js.CallExpr: 104 | for i := range n.Args.List { 105 | n.Args.List[i].Value = convExpr(n.Args.List[i].Value) 106 | } 107 | 108 | if dot, ok := n.X.(*js.DotExpr); ok { 109 | args := js.Args{ 110 | List: make([]js.Arg, 2+len(n.Args.List)), 111 | } 112 | 113 | args.List[0] = js.Arg{Value: convExpr(dot.X)} 114 | args.List[1] = js.Arg{Value: &js.LiteralExpr{ 115 | TokenType: js.StringToken, 116 | Data: []byte(fmt.Sprintf(`"%s"`, dot.Y.JS())), 117 | }} 118 | for i := range n.Args.List { 119 | args.List[i+2] = n.Args.List[i] 120 | } 121 | return &js.CallExpr{ 122 | X: &js.Var{Data: []byte("__xssfinder_property_call")}, 123 | Args: args, 124 | } 125 | } else { 126 | args := js.Args{ 127 | List: make([]js.Arg, 1+len(n.Args.List)), 128 | } 129 | args.List[0] = js.Arg{Value: n.X} 130 | for i := range n.Args.List { 131 | args.List[i+1] = n.Args.List[i] 132 | } 133 | return &js.CallExpr{ 134 | X: &js.Var{Data: []byte("__xssfinder_call")}, 135 | Args: args, 136 | } 137 | } 138 | case *js.UnaryExpr: 139 | switch n.Op { 140 | case js.TypeofToken: 141 | var idvar js.IExpr 142 | switch vvar := n.X.(type) { 143 | case *js.Var: 144 | idvar = vvar 145 | case *js.GroupExpr: 146 | idvar = vvar.X 147 | default: 148 | return &js.CallExpr{ 149 | X: &js.Var{Data: []byte("__xssfinder_typeof")}, 150 | Args: js.Args{ 151 | List: []js.Arg{{Value: n.X}}, 152 | }, 153 | } 154 | } 155 | cond := &js.CondExpr{ 156 | Cond: &js.BinaryExpr{ 157 | Op: js.EqEqEqToken, 158 | X: &js.UnaryExpr{ 159 | Op: js.TypeofToken, 160 | X: idvar, 161 | }, 162 | Y: &js.LiteralExpr{ 163 | TokenType: js.StringToken, 164 | Data: []byte(`"undefined"`), 165 | }, 166 | }, 167 | X: &js.Var{Data: []byte("undefined")}, 168 | Y: idvar, 169 | } 170 | return &js.CallExpr{ 171 | X: &js.Var{Data: []byte("__xssfinder_typeof")}, 172 | Args: js.Args{ 173 | List: []js.Arg{{Value: cond}}, 174 | }, 175 | } 176 | } 177 | case *js.NewExpr: 178 | if v, ok := n.X.(*js.Var); ok && string(v.Data) == "Function" { 179 | return &js.CallExpr{ 180 | X: &js.Var{Data: []byte("__xssfinder_new_Function")}, 181 | Args: *n.Args, 182 | } 183 | } 184 | } 185 | return node 186 | } 187 | 188 | // convWalk traverses an AST in depth-first order 189 | func convWalk(n js.INode) { 190 | if n == nil { 191 | return 192 | } 193 | 194 | switch n := n.(type) { 195 | case *js.AST: 196 | convWalk(&n.BlockStmt) 197 | case *js.Var: 198 | return 199 | case *js.BlockStmt: 200 | if n.List != nil { 201 | for i := 0; i < len(n.List); i++ { 202 | convWalk(n.List[i]) 203 | } 204 | } 205 | case *js.EmptyStmt: 206 | return 207 | case *js.ExprStmt: 208 | n.Value = convExpr(n.Value) 209 | case *js.VarDecl: 210 | if n.List != nil { 211 | for i := range n.List { 212 | n.List[i].Default = convExpr(n.List[i].Default) 213 | } 214 | } 215 | case *js.IfStmt: 216 | convWalk(n.Body) 217 | convWalk(n.Else) 218 | n.Cond = convExpr(n.Cond) 219 | case *js.DoWhileStmt: 220 | convWalk(n.Body) 221 | n.Cond = convExpr(n.Cond) 222 | case *js.WhileStmt: 223 | convWalk(n.Body) 224 | n.Cond = convExpr(n.Cond) 225 | case *js.ForStmt: 226 | if n.Body != nil { 227 | convWalk(n.Body) 228 | } 229 | 230 | n.Init = convExpr(n.Init) 231 | n.Cond = convExpr(n.Cond) 232 | n.Post = convExpr(n.Post) 233 | case *js.ForInStmt: 234 | if n.Body != nil { 235 | convWalk(n.Body) 236 | } 237 | 238 | n.Init = convExpr(n.Init) 239 | n.Value = convExpr(n.Value) 240 | case *js.ForOfStmt: 241 | if n.Body != nil { 242 | convWalk(n.Body) 243 | } 244 | 245 | n.Init = convExpr(n.Init) 246 | n.Value = convExpr(n.Value) 247 | case *js.CaseClause: 248 | if n.List != nil { 249 | for i := 0; i < len(n.List); i++ { 250 | convWalk(n.List[i]) 251 | } 252 | } 253 | 254 | n.Cond = convExpr(n.Cond) 255 | case *js.SwitchStmt: 256 | if n.List != nil { 257 | for i := 0; i < len(n.List); i++ { 258 | convWalk(&n.List[i]) 259 | } 260 | } 261 | 262 | n.Init = convExpr(n.Init) 263 | case *js.BranchStmt: 264 | return 265 | case *js.ReturnStmt: 266 | n.Value = convExpr(n.Value) 267 | case *js.WithStmt: 268 | convWalk(n.Body) 269 | n.Cond = convExpr(n.Cond) 270 | case *js.LabelledStmt: 271 | convWalk(n.Value) 272 | case *js.ThrowStmt: 273 | n.Value = convExpr(n.Value) 274 | case *js.TryStmt: 275 | if n.Body != nil { 276 | convWalk(n.Body) 277 | } 278 | 279 | if n.Catch != nil { 280 | convWalk(n.Catch) 281 | } 282 | 283 | if n.Finally != nil { 284 | convWalk(n.Finally) 285 | } 286 | 287 | convWalk(n.Binding) 288 | case *js.DebuggerStmt: 289 | return 290 | case *js.Alias: 291 | return 292 | case *js.ImportStmt: 293 | // TODO 294 | // if n.List != nil { 295 | // for i := 0; i < len(n.List); i++ { 296 | // convWalk(&n.List[i]) 297 | // } 298 | // } 299 | return 300 | case *js.ExportStmt: 301 | // TODO 302 | // if n.List != nil { 303 | // for i := 0; i < len(n.List); i++ { 304 | // convWalk(&n.List[i]) 305 | // } 306 | // } 307 | 308 | // n.Decl = convExpr(n.Decl) 309 | return 310 | case *js.DirectivePrologueStmt: 311 | return 312 | case *js.PropertyName: 313 | // TODO 314 | // convWalk(&n.Literal) 315 | // n.Computed = convExpr(n.Computed) 316 | return 317 | case *js.BindingArray: 318 | // TODO 319 | // if n.List != nil { 320 | // for i := 0; i < len(n.List); i++ { 321 | // convWalk(&n.List[i]) 322 | // } 323 | // } 324 | 325 | // convWalk(n.Rest) 326 | return 327 | case *js.BindingObjectItem: 328 | // TODO 329 | // if n.Key != nil { 330 | // convWalk(n.Key) 331 | // } 332 | 333 | // convWalk(&n.Value) 334 | return 335 | case *js.BindingObject: 336 | // TODO 337 | // if n.List != nil { 338 | // for i := 0; i < len(n.List); i++ { 339 | // convWalk(&n.List[i]) 340 | // } 341 | // } 342 | 343 | // if n.Rest != nil { 344 | // convWalk(n.Rest) 345 | // } 346 | return 347 | case *js.BindingElement: 348 | // convWalk(n.Binding) 349 | n.Default = convExpr(n.Default) 350 | case *js.Params: 351 | if n.List != nil { 352 | for i := range n.List { 353 | n.List[i].Default = convExpr(n.List[i].Default) 354 | } 355 | } 356 | 357 | // convWalk(n.Rest) 358 | case *js.FuncDecl: 359 | convWalk(&n.Body) 360 | convWalk(&n.Params) 361 | 362 | // if n.Name != nil { 363 | // convWalk(n.Name) 364 | // } 365 | case *js.MethodDecl: 366 | convWalk(&n.Body) 367 | convWalk(&n.Params) 368 | // convWalk(&n.Name) 369 | case *js.FieldDefinition: 370 | // convWalk(&n.Name) 371 | n.Init = convExpr(n.Init) 372 | case *js.ClassDecl: 373 | // if n.Name != nil { 374 | // convWalk(n.Name) 375 | // } 376 | 377 | n.Extends = convExpr(n.Extends) 378 | 379 | if n.Definitions != nil { 380 | for i := 0; i < len(n.Definitions); i++ { 381 | convWalk(&n.Definitions[i]) 382 | } 383 | } 384 | 385 | if n.Methods != nil { 386 | for i := 0; i < len(n.Definitions); i++ { 387 | convWalk(&n.Definitions[i]) 388 | } 389 | } 390 | case *js.LiteralExpr: 391 | return 392 | case *js.Element: 393 | n.Value = convExpr(n.Value) 394 | case *js.ArrayExpr: 395 | if n.List != nil { 396 | for i := 0; i < len(n.List); i++ { 397 | n.List[i].Value = convExpr(n.List[i].Value) 398 | } 399 | } 400 | case *js.Property: 401 | if n.Name != nil { 402 | convWalk(n.Name) 403 | } 404 | 405 | n.Value = convExpr(n.Value) 406 | n.Init = convExpr(n.Init) 407 | case *js.ObjectExpr: 408 | if n.List != nil { 409 | for i := 0; i < len(n.List); i++ { 410 | convWalk(&n.List[i]) 411 | } 412 | } 413 | case *js.TemplatePart: 414 | n.Expr = convExpr(n.Expr) 415 | case *js.TemplateExpr: 416 | if n.List != nil { 417 | for i := 0; i < len(n.List); i++ { 418 | convWalk(&n.List[i]) 419 | } 420 | } 421 | 422 | n.Tag = convExpr(n.Tag) 423 | case *js.GroupExpr: 424 | return 425 | case *js.IndexExpr: 426 | return 427 | case *js.DotExpr: 428 | return 429 | case *js.NewTargetExpr: 430 | return 431 | case *js.ImportMetaExpr: 432 | return 433 | case *js.Arg: 434 | return 435 | case *js.Args: 436 | case *js.NewExpr: 437 | case *js.CallExpr: 438 | 439 | case *js.OptChainExpr: 440 | 441 | case *js.UnaryExpr: 442 | 443 | case *js.BinaryExpr: 444 | 445 | case *js.CondExpr: 446 | 447 | case *js.YieldExpr: 448 | n.X = convExpr(n.X) 449 | case *js.ArrowFunc: 450 | convWalk(&n.Body) 451 | convWalk(&n.Params) 452 | case *js.CommaExpr: 453 | for i := range n.List { 454 | n.List[i] = convExpr(n.List[i]) 455 | } 456 | default: 457 | return 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /pkg/chrome/xss/dom/hookparse_test.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | 8 | "github.com/tdewolff/parse/v2" 9 | "github.com/tdewolff/parse/v2/js" 10 | ) 11 | 12 | func TestConvExpression(t *testing.T) { 13 | code := ` 14 | var a; 15 | a += "1"; 16 | a = "1" + "2"; 17 | var b = decodeURI(location.hash.split("#")[1]); 18 | document.write("Hello2 " + b + "!"); 19 | 20 | typeof name; 21 | typeof "123"; 22 | typeof("123"); 23 | if (typeof "123" === "string"){ 24 | console.log("123") 25 | } 26 | if (typeof a === "string"){ 27 | console.log("bbbbb"); 28 | } 29 | var bb = new String("123123"); 30 | console.log(bb); 31 | 32 | function hello(){ 33 | typeof name; 34 | typeof "123"; 35 | typeof("123"); 36 | } 37 | 38 | const sum = new Function('a', 'b', 'return a + b'); 39 | ` 40 | 41 | t.Log(HookParse(code)) 42 | } 43 | 44 | func TestExpression(t *testing.T) { 45 | code := `// document.cookie = '1123'; 46 | var payload = location.hash.substr(1); 47 | window.status = payload; 48 | var retrieved_payload = window.status; 49 | eval(retrieved_payload); 50 | ` 51 | 52 | t.Log(HookParse(code)) 53 | } 54 | 55 | func TestJsParser(t *testing.T) { 56 | code := ` 57 | // a=={"a":"1.2", b:2.2}; 58 | a.b; 59 | a[b]; 60 | a["b"]; 61 | // a += "1"; 62 | b = a + "1"; 63 | var c = a + b; 64 | const name1 = "zznQ"; 65 | let name; 66 | var b = decodeURI(location.hash.split("#")[1]); 67 | document.write("Hello2 " + b + "!");` 68 | 69 | ast, err := js.Parse(parse.NewInputString(code), js.Options{}) 70 | 71 | if err != nil && err != io.EOF { 72 | t.Fatal(err) 73 | } 74 | for i := range ast.List { 75 | t.Logf("%#v\n", ast.List[i]) 76 | t.Logf(ast.List[i].String()) 77 | } 78 | } 79 | 80 | // testing walker https://github.com/tdewolff/parse/blob/master/js/walk_test.go 81 | 82 | type walker struct{} 83 | 84 | func (w *walker) Enter(n js.INode) js.IVisitor { 85 | switch n := n.(type) { 86 | case *js.Var: 87 | if bytes.Equal(n.Data, []byte("x")) { 88 | n.Data = []byte("obj") 89 | } 90 | } 91 | return w 92 | } 93 | 94 | func (w *walker) Exit(n js.INode) {} 95 | 96 | func TestWalk(t *testing.T) { 97 | code := ` 98 | if (true) { 99 | for (i = 0; i < 1; i++) { 100 | x.y = i 101 | } 102 | }` 103 | 104 | ast, err := js.Parse(parse.NewInputString(code), js.Options{}) 105 | if err != nil { 106 | t.Fatal(err) 107 | } 108 | 109 | js.Walk(&walker{}, ast) 110 | 111 | t.Log(ast.JS()) 112 | } 113 | -------------------------------------------------------------------------------- /pkg/chrome/xss/dom/preload.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * thx: 3 | * https://github.com/AsaiKen/dom-based-xss-finder 4 | * License: MIT 5 | */ 6 | 7 | function __xssfinder_push_dmo_vul(sources, sinkLabel) { 8 | let results = []; 9 | sources.forEach( 10 | (source) => { 11 | let r = { 12 | url: location.href, 13 | source, 14 | sink: __xssfinder_set_track_chian(sinkLabel) 15 | } 16 | results.push(r); 17 | } 18 | ) 19 | // chrome Runtime.addBinding ==> xssfinderPushDomVul 20 | window.xssfinderPushDomVul(JSON.stringify(results)); 21 | } 22 | 23 | const __xssfinder_String = function (str, parent) { 24 | this.str = '' + str; 25 | this.sources = []; // 传播记录 26 | parent.sources.forEach(e => this.sources.push(e)); 27 | 28 | this.valueOf = function () { 29 | return this; 30 | }; 31 | 32 | this.toString = function () { 33 | return this.str; 34 | }; 35 | 36 | // str.length 37 | Object.defineProperty(this, 'length', { 38 | set: () => null, 39 | get: () => this.str.length 40 | }); 41 | 42 | // str[i] 43 | for (let i = 0; i < this.str.length; i++) { 44 | Object.defineProperty(this, i, { 45 | set: () => null, 46 | get: () => new __xssfinder_String(this.str[i], this) 47 | }); 48 | } 49 | // patch __xssfinder_String_flag 标识 50 | Object.defineProperty(this, '__xssfinder_String_flag', { 51 | set: () => null, 52 | get: () => true 53 | }); 54 | }; 55 | __xssfinder_String.prototype = String.prototype; 56 | 57 | function __is_xssfinder_string(o) { 58 | return o && o.__xssfinder_String_flag; 59 | } 60 | 61 | function __is_xssfinder_string_html(o) { 62 | // 63 | o = __convert_to_xssfinder_string_if_location(o); 64 | return __is_xssfinder_string(o); 65 | } 66 | 67 | function __is_xssfinder_string_data_html(o) { 68 | // data:text/html, 69 | o = __convert_to_xssfinder_string_if_location(o); 70 | return __is_xssfinder_string(o); 71 | } 72 | 73 | function __is_xssfinder_string_script(o) { 74 | // alert() 75 | // javascript:alert() 76 | o = __convert_to_xssfinder_string_if_location(o); 77 | return __is_xssfinder_string(o); 78 | } 79 | 80 | function __is_xssfinder_string_url(o) { 81 | // //14.rs 82 | o = __convert_to_xssfinder_string_if_location(o); 83 | return __is_xssfinder_string(o); 84 | } 85 | 86 | function __convert_to_xssfinder_string_if_location(o) { 87 | if (o === window.location) { 88 | o = new __xssfinder_String(o.toString(), { 89 | sources: [__xssfinder_set_track_chian('window.location')], 90 | }); 91 | } 92 | return o; 93 | } 94 | 95 | function __xssfinder_set_track_chian(label) { 96 | return { label, stacktrace: __xssfinder_get_stacktrace() }; 97 | } 98 | 99 | /* 100 | 获取当前堆栈信息 101 | Error 102 | at :1:1 103 | at http://example.com/index.html:12:156 104 | */ 105 | function __xssfinder_get_stacktrace() { 106 | const o = {}; 107 | Error.captureStackTrace(o); 108 | 109 | const regExp = /(https?:\/\/\S+):(\d+):(\d+)/; 110 | 111 | return o.stack.replace(/^Error\n/, '') 112 | .replace(/^\s+at\s+/mg, '') 113 | .split('\n') 114 | .filter(e => regExp.test(e)) 115 | .map(e => { 116 | const m = e.match(regExp); 117 | const url = m[1]; 118 | const line = m[2]; // start from 1 119 | const column = m[3]; // start from 1 120 | return { url, line, column }; 121 | }); 122 | } 123 | 124 | 125 | (function () { 126 | /////////////////////////////////////////////// 127 | // String.prototype track chians 128 | /////////////////////////////////////////////// 129 | const _oldanchor = String.prototype.anchor; 130 | String.prototype.anchor = function () { 131 | if (__is_xssfinder_string(this)) { 132 | const str = _oldanchor.apply(this.toString(), arguments); 133 | return new __xssfinder_String(str, this); 134 | } 135 | return _oldanchor.apply(this, arguments); 136 | }; 137 | 138 | const _oldbig = String.prototype.big; 139 | String.prototype.big = function () { 140 | if (__is_xssfinder_string(this)) { 141 | const str = _oldbig.apply(this.toString(), arguments); 142 | return new __xssfinder_String(str, this); 143 | } 144 | return _oldbig.apply(this, arguments); 145 | }; 146 | 147 | const _oldblink = String.prototype.blink; 148 | String.prototype.blink = function () { 149 | if (__is_xssfinder_string(this)) { 150 | const str = _oldblink.apply(this.toString(), arguments); 151 | return new __xssfinder_String(str, this); 152 | } 153 | return _oldblink.apply(this, arguments); 154 | }; 155 | 156 | const _oldbold = String.prototype.bold; 157 | String.prototype.bold = function () { 158 | if (__is_xssfinder_string(this)) { 159 | const str = _oldbold.apply(this.toString(), arguments); 160 | return new __xssfinder_String(str, this); 161 | } 162 | return _oldbold.apply(this, arguments); 163 | }; 164 | 165 | const _oldcharAt = String.prototype.charAt; 166 | String.prototype.charAt = function () { 167 | if (__is_xssfinder_string(this)) { 168 | const str = _oldcharAt.apply(this.toString(), arguments); 169 | return new __xssfinder_String(str, this); 170 | } 171 | return _oldcharAt.apply(this, arguments); 172 | }; 173 | 174 | const _oldconcat = String.prototype.concat; 175 | String.prototype.concat = function () { 176 | const sources = []; 177 | for (let i = 0; i < arguments.length; i++) { 178 | arguments[i] = __convert_to_xssfinder_string_if_location(arguments[i]); 179 | if (__is_xssfinder_string(arguments[i])) { 180 | arguments[i].sources.forEach(e => sources.push(e)); 181 | } 182 | } 183 | if (__is_xssfinder_string(this)) { 184 | this.sources.forEach(e => sources.push(e)); 185 | } 186 | if (sources.length > 0) { 187 | const str = _oldconcat.apply(this.toString(), arguments); 188 | return new __xssfinder_String(str, { sources }); 189 | } 190 | return _oldconcat.apply(this, arguments); 191 | }; 192 | 193 | const _oldfixed = String.prototype.fixed; 194 | String.prototype.fixed = function () { 195 | if (__is_xssfinder_string(this)) { 196 | const str = _oldfixed.apply(this.toString(), arguments); 197 | return new __xssfinder_String(str, this); 198 | } 199 | return _oldfixed.apply(this, arguments); 200 | }; 201 | 202 | const _oldfontcolor = String.prototype.fontcolor; 203 | String.prototype.fontcolor = function () { 204 | if (__is_xssfinder_string(this)) { 205 | const str = _oldfontcolor.apply(this.toString(), arguments); 206 | return new __xssfinder_String(str, this); 207 | } 208 | return _oldfontcolor.apply(this, arguments); 209 | }; 210 | 211 | const _oldfontsize = String.prototype.fontsize; 212 | String.prototype.fontsize = function () { 213 | if (__is_xssfinder_string(this)) { 214 | const str = _oldfontsize.apply(this.toString(), arguments); 215 | return new __xssfinder_String(str, this); 216 | } 217 | return _oldfontsize.apply(this, arguments); 218 | }; 219 | 220 | 221 | const _olditalics = String.prototype.italics; 222 | String.prototype.italics = function () { 223 | if (__is_xssfinder_string(this)) { 224 | const str = _olditalics.apply(this.toString(), arguments); 225 | return new __xssfinder_String(str, this); 226 | } 227 | return _olditalics.apply(this, arguments); 228 | }; 229 | 230 | 231 | const _oldlink = String.prototype.link; 232 | String.prototype.link = function () { 233 | if (__is_xssfinder_string(this)) { 234 | const str = _oldlink.apply(this.toString(), arguments); 235 | return new __xssfinder_String(str, this); 236 | } 237 | return _oldlink.apply(this, arguments); 238 | }; 239 | 240 | const _oldmatch = String.prototype.match; 241 | String.prototype.match = function () { 242 | // TODO propagate taints of the regexp argument 243 | if (__is_xssfinder_string(this)) { 244 | const res = _oldmatch.apply(this.toString(), arguments); 245 | if (res === null) { 246 | return null; 247 | } 248 | for (let i = 0; i < res.length; i++) { 249 | res[i] = new __xssfinder_String(res[i], this); 250 | } 251 | return res; 252 | } 253 | return _oldmatch.apply(this, arguments); 254 | }; 255 | 256 | // TODO propagate taints of the regexp argument 257 | const _oldmatchAll = String.prototype.matchAll; 258 | String.prototype.matchAll = function () { 259 | if (__is_xssfinder_string(this)) { 260 | const iterator = _oldmatchAll.apply(this.toString(), arguments); 261 | return (function* () {// (需要运行生成器函数,启动生成器) 262 | for (const array of iterator) { 263 | for (let i = 0; i < array.length; i++) { 264 | array[i] = new __xssfinder_String(array[i], this); 265 | } 266 | yield array; 267 | } 268 | })(); 269 | } 270 | return _oldmatchAll.apply(this, arguments); 271 | }; 272 | 273 | const _oldnormalize = String.prototype.normalize; 274 | String.prototype.normalize = function () { 275 | if (__is_xssfinder_string(this)) { 276 | const str = _oldnormalize.apply(this.toString(), arguments); 277 | return new __xssfinder_String(str, this); 278 | } 279 | return _oldnormalize.apply(this, arguments); 280 | }; 281 | 282 | // 'skr'.padEnd(7, 'r') => 'skrrrrr' 283 | const _oldpadEnd = String.prototype.padEnd; 284 | String.prototype.padEnd = function () { 285 | const sources = []; 286 | arguments[1] = __convert_to_xssfinder_string_if_location(arguments[1]); 287 | if (__is_xssfinder_string(arguments[1])) { 288 | arguments[1].sources.forEach(e => sources.push(e)); 289 | } 290 | if (__is_xssfinder_string(this)) { 291 | this.sources.forEach(e => sources.push(e)); 292 | } 293 | if (sources.length > 0) { 294 | const _str = _oldpadEnd.apply(this.toString(), arguments); 295 | return new __xssfinder_String(_str, { sources }); 296 | } 297 | return _oldpadEnd.apply(this, arguments); 298 | }; 299 | 300 | // 'good'.padStart(7, 'g') => 'ggggood' 301 | const _oldpadStart = String.prototype.padStart; 302 | String.prototype.padStart = function () { 303 | const sources = []; 304 | arguments[1] = __convert_to_xssfinder_string_if_location(arguments[1]); 305 | if (__is_xssfinder_string(arguments[1])) { 306 | arguments[1].sources.forEach(e => sources.push(e)); 307 | } 308 | if (__is_xssfinder_string(this)) { 309 | this.sources.forEach(e => sources.push(e)); 310 | } 311 | if (sources.length > 0) { 312 | const str = _oldpadStart.apply(this.toString(), arguments); 313 | return new __xssfinder_String(str, { sources }); 314 | } 315 | return _oldpadStart.apply(this, arguments); 316 | }; 317 | 318 | const _oldrepeat = String.prototype.repeat; 319 | String.prototype.repeat = function () { 320 | if (__is_xssfinder_string(this)) { 321 | const str = _oldrepeat.apply(this.toString(), arguments); 322 | return new __xssfinder_String(str, this); 323 | } 324 | return _oldrepeat.apply(this, arguments); 325 | }; 326 | 327 | const _oldreplace = String.prototype.replace; 328 | String.prototype.replace = function () { 329 | const sources = []; 330 | arguments[1] = __convert_to_xssfinder_string_if_location(arguments[1]); 331 | if (__is_xssfinder_string(arguments[1])) { 332 | arguments[1].sources.forEach(e => sources.push(e)); 333 | } 334 | if (__is_xssfinder_string(this)) { 335 | this.sources.forEach(e => sources.push(e)); 336 | } 337 | if (sources.length > 0) { 338 | const str = _oldreplace.apply(this.toString(), arguments); 339 | return new __xssfinder_String(str, { sources }); 340 | } 341 | return _oldreplace.apply(this, arguments); 342 | }; 343 | 344 | const _oldslice = String.prototype.slice; 345 | String.prototype.slice = function () { 346 | if (__is_xssfinder_string(this)) { 347 | const str = _oldslice.apply(this.toString(), arguments); 348 | return new __xssfinder_String(str, this); 349 | } 350 | return _oldslice.apply(this, arguments); 351 | }; 352 | 353 | const _oldsmall = String.prototype.small; 354 | String.prototype.small = function () { 355 | if (__is_xssfinder_string(this)) { 356 | const str = _oldsmall.apply(this.toString(), arguments); 357 | return new __xssfinder_String(str, this); 358 | } 359 | return _oldsmall.apply(this, arguments); 360 | }; 361 | 362 | const _oldsplite = String.prototype.split; 363 | String.prototype.split = function () { 364 | if (__is_xssfinder_string(this)) { 365 | const array = _oldsplite.apply(this.toString(), arguments); 366 | for (let i = 0; i < array.length; i++) { 367 | array[i] = new __xssfinder_String(array[i], this); 368 | } 369 | return array; 370 | } 371 | return _oldsplite.apply(this, arguments); 372 | }; 373 | 374 | const _oldstrike = String.prototype.strike; 375 | String.prototype.strike = function () { 376 | if (__is_xssfinder_string(this)) { 377 | const str = _oldstrike.apply(this.toString(), arguments); 378 | return new __xssfinder_String(str, this); 379 | } 380 | return _oldstrike.apply(this, arguments); 381 | }; 382 | 383 | const _oldsub = String.prototype.sub; 384 | String.prototype.sub = function () { 385 | if (__is_xssfinder_string(this)) { 386 | const str = _oldsub.apply(this.toString(), arguments); 387 | return new __xssfinder_String(str, this); 388 | } 389 | return _oldsub.apply(this, arguments); 390 | }; 391 | 392 | const _oldsubstr = String.prototype.substr; 393 | String.prototype.substr = function () { 394 | if (__is_xssfinder_string(this)) { 395 | const str = _oldsubstr.apply(this.toString(), arguments); 396 | return new __xssfinder_String(str, this); 397 | } 398 | return _oldsubstr.apply(this, arguments); 399 | }; 400 | 401 | const _oldsubstring = String.prototype.substring; 402 | String.prototype.substring = function () { 403 | if (__is_xssfinder_string(this)) { 404 | const str = _oldsubstring.apply(this.toString(), arguments); 405 | return new __xssfinder_String(str, this); 406 | } 407 | return _oldsubstring.apply(this, arguments); 408 | }; 409 | 410 | const _oldsup = String.prototype.sup; 411 | String.prototype.sup = function () { 412 | if (__is_xssfinder_string(this)) { 413 | const str = _oldsup.apply(this.toString(), arguments); 414 | return new __xssfinder_String(str, this); 415 | } 416 | return _oldsup.apply(this, arguments); 417 | }; 418 | 419 | const _oldtoLocaleLowerCase = String.prototype.toLocaleLowerCase; 420 | String.prototype.toLocaleLowerCase = function () { 421 | if (__is_xssfinder_string(this)) { 422 | const str = _oldtoLocaleLowerCase.apply(this.toString(), arguments); 423 | return new __xssfinder_String(str, this); 424 | } 425 | return _oldtoLocaleLowerCase.apply(this, arguments); 426 | }; 427 | 428 | const _oldtoLocaleUpperCase = String.prototype.toLocaleUpperCase; 429 | String.prototype.toLocaleUpperCase = function () { 430 | if (__is_xssfinder_string(this)) { 431 | const str = _oldtoLocaleUpperCase.apply(this.toString(), arguments); 432 | return new __xssfinder_String(str, this); 433 | } 434 | return _oldtoLocaleUpperCase.apply(this, arguments); 435 | }; 436 | 437 | const _oldtoLowerCase = String.prototype.toLowerCase; 438 | String.prototype.toLowerCase = function () { 439 | if (__is_xssfinder_string(this)) { 440 | const str = _oldtoLowerCase.apply(this.toString(), arguments); 441 | return new __xssfinder_String(str, this); 442 | } 443 | return _oldtoLowerCase.apply(this, arguments); 444 | }; 445 | 446 | const _oldtoUpperCase = String.prototype.toUpperCase; 447 | String.prototype.toUpperCase = function () { 448 | if (__is_xssfinder_string(this)) { 449 | const str = _oldtoUpperCase.apply(this.toString(), arguments); 450 | return new __xssfinder_String(str, this); 451 | } 452 | return _oldtoUpperCase.apply(this, arguments); 453 | }; 454 | 455 | const _oldtrim = String.prototype.trim; 456 | String.prototype.trim = function () { 457 | if (__is_xssfinder_string(this)) { 458 | const str = _oldtrim.apply(this.toString(), arguments); 459 | return new __xssfinder_String(str, this); 460 | } 461 | return _oldtrim.apply(this, arguments); 462 | }; 463 | 464 | const _oldtrimEnd = String.prototype.trimEnd; 465 | String.prototype.trimEnd = function () { 466 | if (__is_xssfinder_string(this)) { 467 | const str = _oldtrimEnd.apply(this.toString(), arguments); 468 | return new __xssfinder_String(str, this); 469 | } 470 | return _oldtrimEnd.apply(this, arguments); 471 | }; 472 | 473 | const _oldtrimStart = String.prototype.trimStart; 474 | String.prototype.trimStart = function () { 475 | if (__is_xssfinder_string(this)) { 476 | const str = _oldtrimStart.apply(this.toString(), arguments); 477 | return new __xssfinder_String(str, this); 478 | } 479 | return _oldtrimStart.apply(this, arguments); 480 | }; 481 | 482 | // skip String.prototype.toString, which is overwritten in __xssfinder_String 483 | // skip String.prototype.valueOf, which is overwritten in __xssfinder_String 484 | 485 | /////////////////////////////////////////////// 486 | // RegExp.prototype 487 | /////////////////////////////////////////////// 488 | 489 | const _oldregExpPrototypeExec = RegExp.prototype.exec; 490 | RegExp.prototype.exec = function () { 491 | const array = _oldregExpPrototypeExec.apply(this, arguments); 492 | if (array !== null && __is_xssfinder_string(arguments[0])) { 493 | for (let i = 0; i < array.length; i++) { 494 | array[i] = new __xssfinder_String(array[i], arguments[0]); 495 | } 496 | } 497 | return array; 498 | }; 499 | 500 | /////////////////////////////////////////////// 501 | // global functions 502 | /////////////////////////////////////////////// 503 | 504 | const _olddecodeURI = decodeURI; 505 | decodeURI = function (URI) { 506 | URI = __convert_to_xssfinder_string_if_location(URI); 507 | if (__is_xssfinder_string(URI)) { 508 | const str = _olddecodeURI.apply(this, [URI.toString()]); 509 | return new __xssfinder_String(str, URI); 510 | } 511 | return _olddecodeURI.apply(this, arguments); 512 | }; 513 | 514 | const _oldencodeURI = encodeURI; 515 | encodeURI = function (URI) { 516 | URI = __convert_to_xssfinder_string_if_location(URI); 517 | if (__is_xssfinder_string(URI)) { 518 | const str = _oldencodeURI.apply(this, [URI.toString()]); 519 | return new __xssfinder_String(str, URI); 520 | } 521 | return _oldencodeURI.apply(this, arguments); 522 | }; 523 | 524 | const _olddecodeURIComponent = decodeURIComponent; 525 | decodeURIComponent = function (URI) { 526 | URI = __convert_to_xssfinder_string_if_location(URI); 527 | if (__is_xssfinder_string(URI)) { 528 | const str = _olddecodeURIComponent.apply(this, [URI.toString()]); 529 | return new __xssfinder_String(str, URI); 530 | } 531 | return _olddecodeURIComponent.apply(this, arguments); 532 | }; 533 | 534 | const _encodeURIComponent = encodeURIComponent; 535 | encodeURIComponent = function (URI) { 536 | URI = __convert_to_xssfinder_string_if_location(URI); 537 | if (__is_xssfinder_string(URI)) { 538 | const str = _encodeURIComponent.apply(this, [URI.toString()]); 539 | return new __xssfinder_String(str, URI); 540 | } 541 | return _encodeURIComponent.apply(this, arguments); 542 | }; 543 | 544 | const _oldunescape = unescape; 545 | unescape = function (escapedString) { 546 | escapedString = __convert_to_xssfinder_string_if_location(escapedString); 547 | if (__is_xssfinder_string(escapedString)) { 548 | const str = _oldunescape.apply(this, [escapedString.toString()]); 549 | return new __xssfinder_String(str, escapedString); 550 | } 551 | return _oldunescape.apply(this, arguments); 552 | }; 553 | 554 | const _oldescape = escape; 555 | escape = function (string) { 556 | string = __convert_to_xssfinder_string_if_location(string); 557 | if (__is_xssfinder_string(string)) { 558 | const str = _oldescape.apply(this, [string.toString()]); 559 | return new __xssfinder_String(str, string); 560 | } 561 | return _oldescape.apply(this, arguments); 562 | }; 563 | 564 | const _oldpostMessage = postMessage; 565 | postMessage = function (message) { 566 | if (__is_xssfinder_string(message)) { 567 | arguments[0] = message.toString(); 568 | } 569 | return _oldpostMessage.apply(this, arguments); 570 | }; 571 | 572 | /////////////////////////////////////////////// 573 | // window.localStorage 574 | // https://developer.mozilla.org/zh-CN/docs/Web/API/Window/localStorage 575 | /////////////////////////////////////////////// 576 | const _localStorage_getItem = window.localStorage.getItem; 577 | window.localStorage.getItem = function (keyname) { 578 | let sources = []; 579 | if (__is_xssfinder_string(keyname)) { 580 | keyname.sources.forEach(e => sources.push(e)); 581 | } 582 | sources.push(__xssfinder_set_track_chian('window.localStorage.getItem("' + keyname + '")')); 583 | 584 | let val = _localStorage_getItem.apply(this, [keyname.toString()]); 585 | return parseObject(val, sources) 586 | }; 587 | /////////////////////////////////////////////// 588 | // window.sessionStorage 589 | // https://developer.mozilla.org/zh-CN/docs/Web/API/Window/sessionStorage 590 | /////////////////////////////////////////////// 591 | const _sessionStorage_getItem = window.sessionStorage.getItem; 592 | window.sessionStorage.getItem = function (keyname) { 593 | let sources = []; 594 | if (__is_xssfinder_string(keyname)) { 595 | keyname.sources.forEach(e => sources.push(e)); 596 | } 597 | sources.push(__xssfinder_set_track_chian('window.sessionStorage.getItem("' + keyname + '")')); 598 | 599 | let val = _sessionStorage_getItem.apply(this, [keyname.toString()]); 600 | return parseObject(val, sources) 601 | }; 602 | })(); 603 | 604 | /*********************************************/ 605 | // window.Storage 606 | /*********************************************/ 607 | 608 | function parseObject(val, sources) { 609 | switch (typeof val) { 610 | case 'string': 611 | return new __xssfinder_String(val.toString(), { sources: sources }); 612 | case 'number': 613 | // TODO custom number 614 | val.____xssfinder_String_flag = true; 615 | val['sources'] = []; 616 | sources.forEach(e => val['sources'].push(e)); 617 | return val 618 | case 'boolean': 619 | val.____xssfinder_String_flag = true; 620 | val['sources'] = []; 621 | sources.forEach(e => val['sources'].push(e)); 622 | return val 623 | case 'object': 624 | for (const key in val) { 625 | val[key] = parseObject(val, sources); 626 | } 627 | return val; 628 | case 'undefined': 629 | return val; 630 | case 'symbol': 631 | return val; 632 | } 633 | } 634 | 635 | /*********************************************/ 636 | // sinks 637 | /*********************************************/ 638 | 639 | 640 | (function () { 641 | /////////////////////////////////////////////// 642 | // Range.prototype 643 | /////////////////////////////////////////////// 644 | 645 | const _rangeCreateContextualFragment = Range.prototype.createContextualFragment; 646 | Range.prototype.createContextualFragment = function (fragment) { 647 | if (__is_xssfinder_string_html(fragment)) { 648 | __xssfinder_push_dmo_vul(fragment.sources, 'Range.prototype.createContextualFragment()'); 649 | } 650 | return _rangeCreateContextualFragment.apply(this, arguments); 651 | }; 652 | 653 | /////////////////////////////////////////////// 654 | // document 655 | /////////////////////////////////////////////// 656 | 657 | const _documentWrite = document.write; 658 | document.write = function (...text) { 659 | for (let i = 0; i < text.length; i++) { 660 | if (__is_xssfinder_string_html(text[i])) { 661 | __xssfinder_push_dmo_vul(text[i].sources, 'document.write()'); 662 | } 663 | } 664 | return _documentWrite.apply(this, arguments); 665 | }; 666 | 667 | const documentWriteln = document.writeln; 668 | document.writeln = function (...text) { 669 | for (let i = 0; i < text.length; i++) { 670 | if (__is_xssfinder_string_html(text[i])) { 671 | __xssfinder_push_dmo_vul(text[i].sources, 'document.writeln()'); 672 | } 673 | } 674 | return documentWriteln.apply(this, arguments); 675 | }; 676 | 677 | /////////////////////////////////////////////// 678 | // global functions 679 | /////////////////////////////////////////////// 680 | 681 | const _eval = eval; 682 | eval = function (x) { 683 | if (__is_xssfinder_string_script(x)) { 684 | __xssfinder_push_dmo_vul(x.sources, 'eval()'); 685 | // eval requires toString() 686 | try { 687 | return _eval.apply(this, [x.toString()]); 688 | } finally { 689 | return undefined; 690 | } 691 | } 692 | return _eval.apply(this, arguments); 693 | }; 694 | 695 | const _setInterval = setInterval; 696 | setInterval = function (handler) { 697 | if (__is_xssfinder_string_script(handler)) { 698 | __xssfinder_push_dmo_vul(handler.sources, 'setTimeout()'); 699 | } 700 | return _setInterval.apply(this, arguments); 701 | }; 702 | 703 | const _setTimeout = setTimeout; 704 | setTimeout = function (handler) { 705 | if (__is_xssfinder_string_script(handler)) { 706 | __xssfinder_push_dmo_vul(handler.sources, 'setTimeout()'); 707 | } 708 | return _setTimeout.apply(this, arguments); 709 | }; 710 | 711 | })(); 712 | 713 | 714 | // @asthook: +,+= 715 | function __xssfinder_plus(left, right) { 716 | left = __convert_to_xssfinder_string_if_location(left); 717 | right = __convert_to_xssfinder_string_if_location(right); 718 | 719 | const sources = []; 720 | if (__is_xssfinder_string(left)) { 721 | left.sources.forEach(e => sources.push(e)); 722 | } 723 | if (__is_xssfinder_string(right)) { 724 | right.sources.forEach(e => sources.push(e)); 725 | } 726 | if (sources.length > 0) { 727 | return new __xssfinder_String('' + left + right, { sources }); 728 | } 729 | 730 | try { 731 | return left + right; 732 | } catch (e) { 733 | return left.toString() + right.toString(); 734 | } 735 | } 736 | 737 | // @asthook: object.key || object[key] 738 | function __xssfinder_get(object, key) { 739 | if (object === window.location) { 740 | switch (key) { 741 | case 'hash': 742 | case 'href': 743 | case 'pathname': 744 | case 'search': 745 | return new __xssfinder_String(object[key], { 746 | sources: [__xssfinder_set_track_chian('window.location.' + key)], 747 | }); 748 | } 749 | } else if (object === document) { 750 | switch (key) { 751 | case 'documentURI': 752 | case 'baseURI': 753 | case 'URL': 754 | case 'cookie': 755 | return new __xssfinder_String(object[key], { 756 | sources: [__xssfinder_set_track_chian('document.' + key)], 757 | }); 758 | case 'referrer': // referrer - https://developer.mozilla.org/zh-CN/docs/Web/API/Document/referrer 759 | let referrer = object[key]; 760 | if (referrer === '') { 761 | referrer = 'https://zznq.imipy.com'; 762 | } 763 | return new __xssfinder_String(referrer, { 764 | sources: [__xssfinder_set_track_chian('document.' + key)], 765 | }); 766 | } 767 | } else if (object === window) { 768 | switch (key) { 769 | case 'status': // window.status - https://developer.mozilla.org/en-US/docs/Web/API/Window/status 770 | if (window.status !== '' && window.status.startsWith("__xssfinder")) { 771 | let v = window['__xssfinder_status']; 772 | v.sources.push(__xssfinder_set_track_chian('window.status')) 773 | return v 774 | } 775 | return window.status 776 | case 'name': 777 | return new __xssfinder_String(object[key], { 778 | sources: [__xssfinder_set_track_chian('window.' + key)], 779 | }); 780 | } 781 | } else if (object === window.localStorage) { 782 | // window.localStorage - https://developer.mozilla.org/zh-CN/docs/Web/API/Window/localStorage 783 | let sources = [__xssfinder_set_track_chian('window.localStorage["' + key + '"]')] 784 | return parseObject(object[key], sources); 785 | } else if (object === window.sessionStorage) { 786 | // window.sessionStorage - https://developer.mozilla.org/zh-CN/docs/Web/API/Window/sessionStorage 787 | let sources = [__xssfinder_set_track_chian('window.sessionStorage["' + key + '"]')] 788 | return parseObject(object[key], sources); 789 | } 790 | return object[key]; 791 | } 792 | 793 | // @asthook: object.key = value || object[key] = value 794 | function __xssfinder_put(object, key, value) { 795 | if (object[key] === window.location && __is_xssfinder_string_script(value)) { 796 | // kill navigation 797 | return; 798 | } else if (object === window.location && key === 'href' && __is_xssfinder_string_script(value) && value.toString() !== object[key]) { 799 | // kill navigation 800 | return; 801 | } 802 | 803 | // window.status - https://developer.mozilla.org/en-US/docs/Web/API/Window/status 804 | if (object === window && key === 'status' && __is_xssfinder_string(value)) { 805 | object['__xssfinder_status'] = value 806 | object['status'] = "__xssfinder" + value.toString() 807 | return; 808 | } 809 | 810 | 811 | if (object instanceof Element && __is_xssfinder_string_html(value)) { 812 | if (key === 'innerHTML' || key === 'outerHTML') { 813 | __xssfinder_push_dmo_vul(value.sources, 'Element.' + key); 814 | } 815 | } else if (object instanceof HTMLScriptElement && __is_xssfinder_string_script(value)) { 816 | if (key === 'text' || key === 'textContent' || key === 'innerText') { 817 | __xssfinder_push_dmo_vul(value.sources, 'HTMLScriptElement.' + key); 818 | } 819 | } else if (object instanceof HTMLScriptElement 820 | && key === 'src' 821 | && __is_xssfinder_string_url(value)) { 822 | __xssfinder_push_dmo_vul(value.sources, 'HTMLScriptElement.src'); 823 | 824 | } else if (object instanceof HTMLEmbedElement 825 | && key === 'src' 826 | && __is_xssfinder_string_url(value)) { 827 | __xssfinder_push_dmo_vul(value.sources, 'HTMLEmbedElement.src'); 828 | 829 | } else if (object instanceof HTMLIFrameElement 830 | && key === 'src' 831 | && __is_xssfinder_string_script(value)) { 832 | __xssfinder_push_dmo_vul(value.sources, 'HTMLIFrameElement.src'); 833 | 834 | } else if (object instanceof HTMLAnchorElement 835 | && key === 'href' 836 | && __is_xssfinder_string_script(value)) { 837 | __xssfinder_push_dmo_vul(value.sources, 'HTMLAnchorElement.href'); 838 | 839 | } else if (object instanceof HTMLFormElement 840 | && key === 'action' 841 | && __is_xssfinder_string_script(value)) { 842 | __xssfinder_push_dmo_vul(value.sources, 'HTMLFormElement.action'); 843 | 844 | } else if (object instanceof HTMLInputElement 845 | && key === 'formAction' 846 | && __is_xssfinder_string_script(value)) { 847 | __xssfinder_push_dmo_vul(value.sources, 'HTMLInputElement.formAction'); 848 | 849 | } else if (object instanceof HTMLButtonElement 850 | && key === 'formAction' 851 | && __is_xssfinder_string_script(value)) { 852 | __xssfinder_push_dmo_vul(value.sources, 'HTMLButtonElement.formAction'); 853 | 854 | } else if (object instanceof HTMLObjectElement 855 | && key === 'data' 856 | && __is_xssfinder_string_data_html(value)) { 857 | __xssfinder_push_dmo_vul(value.sources, 'HTMLObjectElement.data'); 858 | } 859 | return object[key] = value; 860 | } 861 | 862 | // @asthook: function 863 | // example => `const sum = new Function('a', 'b', 'return a + b');` 864 | function __xssfinder_new_Function() { 865 | const f = new Function(...arguments); 866 | const _code = arguments[arguments.length - 1]; 867 | if (__is_xssfinder_string_script(_code)) { 868 | __xssfinder_push_dmo_vul(_code.sources, 'new Function()'); 869 | f.__dombasedxssfinder_str = _code; 870 | } 871 | return f; 872 | } 873 | 874 | // @asthook: == 875 | function __xssfinder_equal(left, right) { 876 | if (__is_xssfinder_string(left)) { 877 | left = left.toString(); 878 | } 879 | if (__is_xssfinder_string(right)) { 880 | right = right.toString(); 881 | } 882 | return left == right; 883 | } 884 | 885 | // @asthook: != 886 | function __xssfinder_notEqual(left, right) { 887 | if (__is_xssfinder_string(left)) { 888 | left = left.toString(); 889 | } 890 | if (__is_xssfinder_string(right)) { 891 | right = right.toString(); 892 | } 893 | return left != right; 894 | } 895 | 896 | // @asthook: === 897 | function __xssfinder_strictEqual(left, right) { 898 | if (__is_xssfinder_string(left)) { 899 | left = left.toString(); 900 | } 901 | if (__is_xssfinder_string(right)) { 902 | right = right.toString(); 903 | } 904 | return left === right; 905 | } 906 | 907 | // @asthook: !== 908 | function __xssfinder_strictNotEqual(left, right) { 909 | if (__is_xssfinder_string(left)) { 910 | left = left.toString(); 911 | } 912 | if (__is_xssfinder_string(right)) { 913 | right = right.toString(); 914 | } 915 | return left !== right; 916 | } 917 | 918 | // @asthook: typeof 919 | function __xssfinder_typeof(o) { 920 | if (__is_xssfinder_string(o)) { 921 | return 'string'; 922 | } 923 | return typeof o; 924 | } 925 | 926 | // @asthook: object.key(...arguments) || object[key](...arguments) 927 | function __xssfinder_property_call(object, key, ...arguments) { 928 | if (object[key] === window.location.assign) { 929 | // cannot overwrite, replace it when called. 930 | return (function (url) { 931 | if (__is_xssfinder_string_script(url)) { 932 | // kill navigation 933 | return; 934 | } 935 | }).apply(object, arguments); 936 | } else if (object[key] === window.location.replace) { 937 | // cannot overwrite, replace it when called. 938 | return (function (url) { 939 | if (__is_xssfinder_string_script(url)) { 940 | // kill navigation 941 | return; 942 | } 943 | }).apply(object, arguments); 944 | } 945 | 946 | if (object instanceof Element && key === 'setAttribute') { 947 | const _elementSetAttribute = object[key]; 948 | return (function (qualifiedName, value) { 949 | if (qualifiedName.startsWith('on') && __is_xssfinder_string_script(value)) { 950 | __xssfinder_push_dmo_vul(value.sources, `Element.setAttribute('${qualifiedName}')`); 951 | } else if (this instanceof HTMLScriptElement && qualifiedName === 'src' && __is_xssfinder_string_url(value)) { 952 | __xssfinder_push_dmo_vul(value.sources, 'HTMLScriptElement.setAttribute(\'src\')'); 953 | } else if (this instanceof HTMLEmbedElement && qualifiedName === 'src' && __is_xssfinder_string_url(value)) { 954 | __xssfinder_push_dmo_vul(value.sources, 'HTMLEmbedElement.setAttribute(\'src\')'); 955 | } else if (this instanceof HTMLIFrameElement && qualifiedName === 'src' && __is_xssfinder_string_script(value)) { 956 | __xssfinder_push_dmo_vul(value.sources, 'HTMLIFrameElement.setAttribute(\'src\')'); 957 | } else if (this instanceof HTMLAnchorElement && qualifiedName === 'href' && __is_xssfinder_string_script(value)) { 958 | __xssfinder_push_dmo_vul(value.sources, 'HTMLAnchorElement.setAttribute(\'href\')'); 959 | } else if (this instanceof HTMLFormElement && qualifiedName === 'action' && __is_xssfinder_string_script(value)) { 960 | __xssfinder_push_dmo_vul(value.sources, 'HTMLFormElement.setAttribute(\'action\')'); 961 | } else if (this instanceof HTMLInputElement && qualifiedName === 'formaction' && __is_xssfinder_string_script(value)) { 962 | __xssfinder_push_dmo_vul(value.sources, 'HTMLInputElement.setAttribute(\'formaction\')'); 963 | } else if (this instanceof HTMLButtonElement && qualifiedName === 'formaction' && __is_xssfinder_string_script(value)) { 964 | __xssfinder_push_dmo_vul(value.sources, 'HTMLButtonElement.setAttribute(\'formaction\')'); 965 | } else if (this instanceof HTMLObjectElement && qualifiedName === 'data' && __is_xssfinder_string_data_html(value)) { 966 | __xssfinder_push_dmo_vul(value.sources, 'HTMLObjectElement.setAttribute(\'data\')'); 967 | } 968 | _elementSetAttribute.apply(this, arguments); 969 | }).apply(object, arguments); 970 | } else if (object instanceof Element && key === 'addEventListener') { 971 | const _elementAddEventListener = object[key]; 972 | return (function (type, listener) { 973 | if (type === 'click' && listener && listener.__dombasedxssfinder_str && __is_xssfinder_string_script(listener.__dombasedxssfinder_str)) { 974 | __xssfinder_push_dmo_vul(listener.__dombasedxssfinder_str.sources, 'Element.addEventListener(\'click\')'); 975 | } 976 | _elementAddEventListener.apply(this, arguments); 977 | }).apply(object, arguments); 978 | } 979 | 980 | return object[key](...arguments); 981 | } 982 | 983 | // @asthook: func(...arguments) 984 | function __xssfinder_call(func, ...arguments) { 985 | if (func === window.location.assign) { 986 | // cannot overwrite, replace it when called. 987 | func = function (url) { 988 | if (__is_xssfinder_string_script(url)) { 989 | // kill navigation 990 | return; 991 | } 992 | }; 993 | } else if (func === window.location.replace) { 994 | // cannot overwrite, replace it when called. 995 | func = function (url) { 996 | if (__is_xssfinder_string_script(url)) { 997 | // kill navigation 998 | return; 999 | } 1000 | }; 1001 | } 1002 | 1003 | return func(...arguments); 1004 | } -------------------------------------------------------------------------------- /pkg/httpdump/http.go: -------------------------------------------------------------------------------- 1 | package httpdump 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "io" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/gokitx/pkgs/urlx" 12 | "github.com/sirupsen/logrus" 13 | ) 14 | 15 | var ( 16 | defaultClient *http.Client = &http.Client{ 17 | Transport: &http.Transport{ 18 | TLSClientConfig: &tls.Config{ 19 | InsecureSkipVerify: true, 20 | }, 21 | }, 22 | } 23 | ) 24 | 25 | func Do(req Request, timeout time.Duration) (Response, error) { 26 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 27 | defer cancel() 28 | 29 | resp := Response{} 30 | hreq, err := http.NewRequest(req.Method, req.URL, nil) 31 | if err != nil { 32 | return resp, err 33 | } 34 | hreq.WithContext(ctx) 35 | hreq.Header = req.Header.Clone() 36 | if req.Method != http.MethodGet { 37 | hreq.PostForm = urlx.CloneUrlValues(req.PostForm) 38 | } 39 | hresp, err := defaultClient.Do(hreq) 40 | if err != nil { 41 | return resp, err 42 | } 43 | 44 | resp.Status = hresp.StatusCode 45 | resp.Header = hresp.Header.Clone() 46 | if resp.Body != nil { 47 | defer hresp.Body.Close() 48 | 49 | buf := bufferPool.Get().(*bytes.Buffer) 50 | buf.Reset() 51 | defer func() { 52 | if buf != nil { 53 | buf.Reset() 54 | bufferPool.Put(buf) 55 | } 56 | buf = nil 57 | }() 58 | 59 | if _, err := io.Copy(buf, hresp.Body); err != nil { 60 | logrus.Errorln("[httputil] copy resp.body error:", err) 61 | } else { 62 | resp.Body = buf.Bytes() 63 | } 64 | } 65 | return resp, nil 66 | } 67 | -------------------------------------------------------------------------------- /pkg/httpdump/http_test.go: -------------------------------------------------------------------------------- 1 | package httpdump 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestDo(t *testing.T) { 10 | req := Request{ 11 | Method: http.MethodGet, 12 | URL: "https://www.baidu.com", 13 | } 14 | 15 | t.Log(Do(req, time.Second)) 16 | 17 | t.Log(req) 18 | } 19 | -------------------------------------------------------------------------------- /pkg/httpdump/httpdump.go: -------------------------------------------------------------------------------- 1 | package httpdump 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "mime/multipart" 7 | "net/http" 8 | "net/textproto" 9 | "net/url" 10 | "sync" 11 | "time" 12 | 13 | "github.com/gokitx/pkgs/urlx" 14 | "github.com/sirupsen/logrus" 15 | ) 16 | 17 | var ( 18 | bufferPool = sync.Pool{ 19 | New: func() any { 20 | return bytes.NewBuffer(make([]byte, 1024)) 21 | }, 22 | } 23 | ) 24 | 25 | type Request struct { 26 | Method string 27 | URL string 28 | Header http.Header 29 | Host string 30 | PostForm url.Values 31 | Cookies []Cookie 32 | Response Response 33 | } 34 | 35 | type Cookie struct { 36 | Name string 37 | Value string 38 | Domain string 39 | Path string 40 | HttpOnly bool 41 | Secure bool 42 | Expires time.Time 43 | } 44 | 45 | type Response struct { 46 | Status int 47 | Header http.Header 48 | Body []byte 49 | } 50 | 51 | func MakeRequest(req *http.Request) Request { 52 | if req.URL.Fragment == "" { 53 | req.URL.Fragment = "zznq" 54 | defer func() { req.URL.Fragment = "" }() 55 | } 56 | 57 | r2 := Request{ 58 | Method: req.Method, 59 | Host: req.Host, 60 | URL: req.URL.String(), 61 | } 62 | r2.Header = req.Header.Clone() 63 | req.ParseForm() 64 | r2.PostForm = urlx.CloneUrlValues(req.PostForm) 65 | return r2 66 | } 67 | 68 | func MakeResponse(req Request, resp *http.Response) Response { 69 | r2 := Response{ 70 | Status: resp.StatusCode, 71 | Header: resp.Header.Clone(), 72 | Body: nil, 73 | } 74 | // reponse setcookies 75 | cookies := resp.Cookies() 76 | for i := range cookies { 77 | if cookies[i].Domain == "" { 78 | cookies[i].Domain = resp.Request.Host 79 | } 80 | } 81 | req.Cookies = append(req.Cookies, dumpCookies(cookies)...) 82 | 83 | if resp.Body != nil { 84 | buf := bufferPool.Get().(*bytes.Buffer) 85 | buf.Reset() 86 | defer func() { 87 | if buf != nil { 88 | buf.Reset() 89 | bufferPool.Put(buf) 90 | } 91 | buf = nil 92 | }() 93 | 94 | if _, err := io.Copy(buf, resp.Body); err != nil { 95 | resp.Body.Close() 96 | logrus.Errorln("[httputil] copy resp.body error:", err) 97 | } else { 98 | resp.Body.Close() 99 | data := buf.Bytes() 100 | r2.Body = data 101 | resp.Body = io.NopCloser(bytes.NewReader(data)) 102 | } 103 | } 104 | return r2 105 | } 106 | 107 | func cloneURL(u *url.URL) *url.URL { 108 | if u == nil { 109 | return nil 110 | } 111 | u2 := new(url.URL) 112 | *u2 = *u 113 | if u.User != nil { 114 | u2.User = new(url.Userinfo) 115 | *u2.User = *u.User 116 | } 117 | return u2 118 | } 119 | 120 | func cloneMultipartForm(f *multipart.Form) *multipart.Form { 121 | if f == nil { 122 | return nil 123 | } 124 | f2 := &multipart.Form{ 125 | Value: (map[string][]string)(http.Header(f.Value).Clone()), 126 | } 127 | if f.File != nil { 128 | m := make(map[string][]*multipart.FileHeader) 129 | for k, vv := range f.File { 130 | vv2 := make([]*multipart.FileHeader, len(vv)) 131 | for i, v := range vv { 132 | vv2[i] = cloneMultipartFileHeader(v) 133 | } 134 | m[k] = vv2 135 | } 136 | f2.File = m 137 | } 138 | return f2 139 | } 140 | 141 | func cloneMultipartFileHeader(fh *multipart.FileHeader) *multipart.FileHeader { 142 | if fh == nil { 143 | return nil 144 | } 145 | fh2 := new(multipart.FileHeader) 146 | *fh2 = *fh 147 | fh2.Header = textproto.MIMEHeader(http.Header(fh.Header).Clone()) 148 | return fh2 149 | } 150 | 151 | func dumpCookies(c []*http.Cookie) []Cookie { 152 | r := make([]Cookie, len(c)) 153 | for i := range c { 154 | r[i] = Cookie{ 155 | Name: c[i].Name, 156 | Value: c[i].Value, 157 | Domain: c[i].Domain, 158 | Path: c[i].Path, 159 | HttpOnly: c[i].HttpOnly, 160 | Secure: c[i].Secure, 161 | Expires: c[i].Expires, 162 | } 163 | } 164 | return r 165 | } 166 | -------------------------------------------------------------------------------- /pkg/mix/mixpayloads.go: -------------------------------------------------------------------------------- 1 | // Package mix used to mix payload with different rules and scopes 2 | package mix 3 | 4 | import ( 5 | "net/url" 6 | "path" 7 | "strings" 8 | 9 | "github.com/gokitx/pkgs/urlx" 10 | ) 11 | 12 | func Payloads(u url.URL, payloads []string, rules []Rule, scopes []Scope) []url.URL { 13 | var urls []url.URL 14 | for _, scope := range scopes { 15 | switch scope { 16 | case ScopeQuery: 17 | urls = append(urls, mixQuery(u, payloads, rules)...) 18 | case ScopePath: 19 | urls = append(urls, mixPath(u, payloads, rules)...) 20 | case ScopeFragment: 21 | urls = append(urls, mixFragment(u, payloads, rules)...) 22 | } 23 | } 24 | return urls 25 | } 26 | 27 | func mixQuery(u url.URL, payloads []string, rules []Rule) []url.URL { 28 | if len(u.Query()) == 0 { 29 | return nil 30 | } 31 | baseQuery := u.Query() 32 | urls := make([]url.URL, len(payloads)*len(rules)*len(baseQuery)) 33 | 34 | var index int 35 | for _, payload := range payloads { 36 | for key := range baseQuery { 37 | for _, rule := range rules { 38 | u.RawQuery = generateQueryWithRule( 39 | urlx.CloneUrlValues(baseQuery), 40 | key, payload, rule, 41 | ).Encode() 42 | urls[index] = u 43 | index++ 44 | } 45 | } 46 | } 47 | return urls 48 | } 49 | 50 | func generateQueryWithRule(query url.Values, key, payload string, rule Rule) url.Values { 51 | switch rule { 52 | case RuleAppendPrefix: 53 | query.Set(key, payload+query.Get(key)) 54 | return query 55 | case RuleAppendSuffix: 56 | query.Set(key, query.Get(key)+payload) 57 | return query 58 | case RuleReplace: 59 | query.Set(key, payload) 60 | return query 61 | } 62 | return query 63 | } 64 | 65 | func mixPath(u url.URL, payloads []string, rules []Rule) []url.URL { 66 | var urls []url.URL 67 | paths := strings.Split(u.Path, "/") 68 | if len(paths) <= 1 { 69 | return nil 70 | } 71 | for _, payload := range payloads { 72 | for index := range paths { 73 | for _, rule := range rules { 74 | if paths[index] == "" { 75 | continue 76 | } 77 | brefore := paths[index] 78 | paths[index] = generateStrWithRule(brefore, payload, rule) 79 | u.Path = path.Join(paths...) 80 | paths[index] = brefore 81 | urls = append(urls, u) 82 | } 83 | } 84 | } 85 | return urls 86 | } 87 | 88 | func generateStrWithRule(old, payload string, rule Rule) string { 89 | switch rule { 90 | case RuleAppendPrefix: 91 | return payload + old 92 | case RuleAppendSuffix: 93 | return old + payload 94 | case RuleReplace: 95 | return payload 96 | } 97 | return old 98 | } 99 | 100 | func mixFragment(u url.URL, payloads []string, rules []Rule) []url.URL { 101 | var urls []url.URL 102 | if u.Fragment == "" { 103 | return nil 104 | } 105 | fragment := u.Fragment 106 | for _, payload := range payloads { 107 | for _, rule := range rules { 108 | u.Fragment = generateStrWithRule(fragment, payload, rule) 109 | urls = append(urls, u) 110 | } 111 | } 112 | return urls 113 | } 114 | 115 | // mixFilter decides whether a payload or url is allowed to be mixed or not. 116 | // TODO: filter numeric or string types 117 | type mixFilter int 118 | 119 | const ( 120 | FilterNumeric mixFilter = iota + 1 121 | FilterString 122 | FilterIP 123 | FilterDomain 124 | FilterURI 125 | FilterEmail 126 | ) 127 | 128 | type Rule int 129 | 130 | const ( 131 | // RuleAppendPrefix appends the payload to the beginning of the string 132 | RuleAppendPrefix Rule = iota + 1 133 | // RuleAppendSuffix appends the payload to the end of the string 134 | RuleAppendSuffix 135 | // RuleReplace replaces the string with the payload 136 | RuleReplace 137 | ) 138 | 139 | var DefaultMixRules = []Rule{RuleAppendPrefix, RuleAppendSuffix, RuleReplace} 140 | 141 | type Scope int 142 | 143 | const ( 144 | ScopeQuery Scope = iota + 1 145 | ScopePath 146 | ScopeFragment 147 | ) 148 | 149 | var DefaultScopes = []Scope{ScopeQuery, ScopeFragment} 150 | -------------------------------------------------------------------------------- /pkg/mix/mixpayloads_test.go: -------------------------------------------------------------------------------- 1 | package mix 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | var urlTestCases = []struct { 11 | urlOBJ url.URL 12 | }{ 13 | { 14 | urlOBJ: url.URL{ 15 | Scheme: "https", 16 | Host: "www.hackerone.com", 17 | Path: "/pathA/pathB/pathC", 18 | RawQuery: "queryA=111&queryB=222", 19 | Fragment: "fragment", 20 | }, 21 | }, 22 | } 23 | 24 | var payloads = []string{ 25 | "moond4rk", 26 | "buzz2d0", 27 | } 28 | 29 | func TestMixPayloads(t *testing.T) { 30 | t.Parallel() 31 | var urls []url.URL 32 | for _, testCase := range urlTestCases { 33 | urls = Payloads(testCase.urlOBJ, payloads, DefaultMixRules, DefaultScopes) 34 | } 35 | for _, v := range urls { 36 | fmt.Println(v.String()) 37 | } 38 | } 39 | 40 | func TestPathMixPayloads(t *testing.T) { 41 | t.Parallel() 42 | var urls []url.URL 43 | for _, testCase := range urlTestCases { 44 | urls = Payloads(testCase.urlOBJ, payloads, DefaultMixRules, []Scope{ScopePath}) 45 | } 46 | for _, v := range urls { 47 | fmt.Println(v.String()) 48 | } 49 | } 50 | 51 | func TestMixQuery(t *testing.T) { 52 | t.Parallel() 53 | var urls []url.URL 54 | var queryIndex int 55 | for _, tc := range urlTestCases { 56 | queryIndex += len(tc.urlOBJ.Query()) 57 | urls = append(urls, mixQuery(tc.urlOBJ, payloads, DefaultMixRules)...) 58 | } 59 | if len(urls) != len(urlTestCases)*len(payloads)*len(DefaultMixRules)*queryIndex { 60 | t.Errorf("Expected %d urls, got %d", len(urlTestCases)*len(payloads)*len(DefaultMixRules)*queryIndex, len(urls)) 61 | } 62 | } 63 | 64 | func TestMixPath(t *testing.T) { 65 | t.Parallel() 66 | var urls []url.URL 67 | pathIndex := 0 68 | for _, tc := range urlTestCases { 69 | for _, p := range strings.Split(tc.urlOBJ.Path, "/") { 70 | if p != "" { 71 | pathIndex++ 72 | } 73 | } 74 | urls = append(urls, mixPath(tc.urlOBJ, payloads, DefaultMixRules)...) 75 | } 76 | if len(urls) != len(urlTestCases)*len(payloads)*len(DefaultMixRules)*pathIndex { 77 | t.Errorf("Expected %d urls, got %d", len(urlTestCases)*len(payloads)*len(DefaultMixRules)*pathIndex, len(urls)) 78 | } 79 | } 80 | 81 | func TestMixFragment(t *testing.T) { 82 | t.Parallel() 83 | var urls []url.URL 84 | var index int 85 | for _, tc := range urlTestCases { 86 | if tc.urlOBJ.Fragment != "" { 87 | index++ 88 | } 89 | urls = append(urls, mixFragment(tc.urlOBJ, payloads, DefaultMixRules)...) 90 | } 91 | if len(urls) != len(urlTestCases)*len(payloads)*len(DefaultMixRules)*index { 92 | t.Errorf("Expected %d urls, got %d", len(urlTestCases)*len(payloads)*len(DefaultMixRules), len(urls)) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkg/notify/dingbot/dingbot.go: -------------------------------------------------------------------------------- 1 | package dingbot 2 | 3 | import ( 4 | "bytes" 5 | "crypto/hmac" 6 | "crypto/sha256" 7 | "encoding/base64" 8 | "encoding/json" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "strconv" 13 | "time" 14 | ) 15 | 16 | const defaultDingTalkURL = `https://oapi.dingtalk.com/robot/send` 17 | 18 | type Dingbot struct { 19 | Token string `yaml:"token" json:"token"` 20 | Secret string `yaml:"secret" json:"secret"` 21 | } 22 | 23 | func New(token, secret string) *Dingbot { 24 | if token == "" || secret == "" { 25 | return nil 26 | } 27 | return &Dingbot{ 28 | Token: token, 29 | Secret: secret, 30 | } 31 | } 32 | 33 | func (d *Dingbot) Notify(title, text string) error { 34 | url, err := d.makeSign() 35 | if err != nil { 36 | return err 37 | } 38 | msg, err := d.makeMarkdownMsg(title, text) 39 | if err != nil { 40 | return err 41 | } 42 | resp, err := http.Post(url, "application/json; charset=utf-8", bytes.NewReader(msg)) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | data, err := io.ReadAll(resp.Body) 48 | if err != nil { 49 | return err 50 | } 51 | defer resp.Body.Close() 52 | if resp.StatusCode != http.StatusOK { 53 | return fmt.Errorf("status: %d, body: %v", resp.StatusCode, string(data)) 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func (d *Dingbot) makeSign() (string, error) { 60 | var ( 61 | timestamp, sign string 62 | ) 63 | timestamp = strconv.FormatInt(time.Now().Unix()*1000, 10) 64 | h := hmac.New(sha256.New, []byte(d.Secret)) 65 | if _, err := h.Write([]byte(fmt.Sprintf("%s\n%s", timestamp, d.Secret))); err != nil { 66 | return "", err 67 | } 68 | sign = base64.StdEncoding.EncodeToString(h.Sum(nil)) 69 | 70 | return fmt.Sprintf("%s?access_token=%s×tamp=%s&sign=%s", defaultDingTalkURL, d.Token, timestamp, sign), nil 71 | } 72 | 73 | type MarkdownMsg struct { 74 | MsgType string `json:"msgtype"` 75 | Markdown struct { 76 | Title string `json:"title"` 77 | Text string `json:"text"` 78 | } `json:"markdown"` 79 | } 80 | 81 | func (d *Dingbot) makeMarkdownMsg(title, text string) ([]byte, error) { 82 | msg := &MarkdownMsg{ 83 | MsgType: "markdown", 84 | } 85 | msg.Markdown.Title = title 86 | msg.Markdown.Text = fmt.Sprintf("### %s\n---\n%s", title, text) 87 | data, err := json.Marshal(msg) 88 | if err != nil { 89 | return nil, err 90 | } 91 | return data, nil 92 | } 93 | -------------------------------------------------------------------------------- /pkg/notify/dingbot/dingbot_test.go: -------------------------------------------------------------------------------- 1 | package dingbot 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestDingbot(t *testing.T) { 9 | d := New(os.Getenv("dingbot_token"), os.Getenv("dingbot_secret")) 10 | d.Notify("http://localhost:8080/", `- type dom`) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/notify/notify.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/Buzz2d0/xssfinder/pkg/notify/dingbot" 8 | "golang.org/x/sync/errgroup" 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | type Notifier interface { 13 | Notify(title, text string) error 14 | } 15 | 16 | type Notifiers []Notifier 17 | 18 | var _ Notifier = Notifiers{} 19 | 20 | func (n Notifiers) Notify(title, text string) error { 21 | var eg errgroup.Group 22 | for _, notifier := range n { 23 | nn := notifier 24 | eg.Go(func() error { 25 | return nn.Notify(title, text) 26 | }) 27 | } 28 | return eg.Wait() 29 | } 30 | 31 | type notifiersConfig struct { 32 | Dingbot dingbot.Dingbot `yaml:"dingbot" json:"dingbot"` 33 | } 34 | 35 | func NewNotifierWithYaml(file string) (Notifier, error) { 36 | f, err := os.Open(file) 37 | if err != nil { 38 | return nil, err 39 | } 40 | defer f.Close() 41 | 42 | data, err := io.ReadAll(f) 43 | if err != nil { 44 | return nil, err 45 | } 46 | t := ¬ifiersConfig{} 47 | if err := yaml.Unmarshal(data, t); err != nil { 48 | return nil, err 49 | } 50 | return Notifiers{dingbot.New(t.Dingbot.Token, t.Dingbot.Secret)}, nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/notify/notify_test.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import "testing" 4 | 5 | func TestNotifiers(t *testing.T) { 6 | n, err := NewNotifierWithYaml("notifier.yaml") 7 | t.Log(err) 8 | t.Log(n.Notify("http://localhost:8080/", `- type dom`)) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/parser/html/html.go: -------------------------------------------------------------------------------- 1 | package html 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/Buzz2d0/xssfinder/pkg/parser/javascript" 7 | "github.com/gokitx/pkgs/slicex" 8 | "golang.org/x/net/html" 9 | ) 10 | 11 | const ( 12 | inputTag = "input" 13 | scriptTag = "script" 14 | styleTag = "style" 15 | ) 16 | 17 | func GetParams(r io.Reader) ([]string, error) { 18 | doc, err := html.Parse(r) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | params := make([]string, 0) 24 | var f func(*html.Node) 25 | f = func(n *html.Node) { 26 | switch n.Type { 27 | case html.ElementNode: 28 | switch n.Data { 29 | case inputTag: 30 | for _, a := range n.Attr { 31 | if a.Key == "name" { 32 | params = append(params, a.Val) 33 | break 34 | } 35 | } 36 | case scriptTag: 37 | if n.FirstChild != nil && 38 | n.FirstChild.Type == html.TextNode { 39 | if vars, err := javascript.GetAllVariable(n.FirstChild.Data); err == nil { 40 | params = append(params, vars...) 41 | } 42 | } 43 | } 44 | } 45 | for c := n.FirstChild; c != nil; c = c.NextSibling { 46 | f(c) 47 | } 48 | } 49 | 50 | f(doc) 51 | 52 | return slicex.RevemoRepByMap(params), nil 53 | } 54 | -------------------------------------------------------------------------------- /pkg/parser/html/html_test.go: -------------------------------------------------------------------------------- 1 | package html 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestGetParams(t *testing.T) { 9 | doc := ` 10 | 11 | 12 | 13 | 14 | 26 | ` 27 | t.Log(GetParams(strings.NewReader(doc))) 28 | } 29 | -------------------------------------------------------------------------------- /pkg/parser/html/search.go: -------------------------------------------------------------------------------- 1 | package html 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | 7 | "golang.org/x/net/html" 8 | ) 9 | 10 | type LocationType uint8 11 | 12 | const ( 13 | InTag LocationType = iota + 1 14 | InComment 15 | InHtml 16 | Inscript 17 | InStyle 18 | InAttr 19 | ) 20 | 21 | type ReflexLocation struct { 22 | Type LocationType 23 | TagName string // in-html 24 | Content string 25 | } 26 | 27 | func MarkReflexLocation(param string, r io.Reader) ([]ReflexLocation, error) { 28 | doc, err := html.Parse(r) 29 | if err != nil { 30 | return nil, err 31 | } 32 | var ( 33 | f func(*html.Node) 34 | ls []ReflexLocation = make([]ReflexLocation, 0) 35 | ) 36 | f = func(n *html.Node) { 37 | switch n.Type { 38 | case html.ElementNode: 39 | if strings.Contains(n.Data, param) { 40 | ls = append(ls, ReflexLocation{ 41 | Type: InTag, 42 | TagName: n.Data, 43 | Content: n.Data, 44 | }) 45 | } 46 | for _, a := range n.Attr { 47 | if strings.Contains(a.Key, param) { 48 | ls = append(ls, ReflexLocation{ 49 | Type: InAttr, 50 | Content: a.Key, 51 | }) 52 | } else if strings.Contains(a.Val, param) { 53 | ls = append(ls, ReflexLocation{ 54 | Type: InAttr, 55 | Content: a.Val, 56 | }) 57 | } 58 | } 59 | switch n.Data { 60 | case scriptTag: 61 | if n.FirstChild != nil && 62 | n.FirstChild.Type == html.TextNode && 63 | strings.Contains(n.FirstChild.Data, param) { 64 | ls = append(ls, ReflexLocation{ 65 | Type: Inscript, 66 | Content: strings.TrimSpace(n.FirstChild.Data), 67 | }) 68 | } 69 | case styleTag: 70 | if n.FirstChild != nil && 71 | n.FirstChild.Type == html.TextNode && 72 | strings.Contains(n.FirstChild.Data, param) { 73 | ls = append(ls, ReflexLocation{ 74 | Type: InStyle, 75 | Content: strings.TrimSpace(n.FirstChild.Data), 76 | }) 77 | } 78 | default: 79 | if n.FirstChild != nil && 80 | n.FirstChild.Type == html.TextNode && 81 | strings.Contains(n.FirstChild.Data, param) { 82 | ls = append(ls, ReflexLocation{ 83 | Type: InHtml, 84 | TagName: n.Data, 85 | Content: strings.TrimSpace(n.FirstChild.Data), 86 | }) 87 | } 88 | } 89 | case html.CommentNode: 90 | if strings.Contains(n.Data, param) { 91 | ls = append(ls, ReflexLocation{ 92 | Type: InComment, 93 | Content: strings.TrimSpace(n.Data), 94 | }) 95 | } 96 | } 97 | for c := n.FirstChild; c != nil; c = c.NextSibling { 98 | f(c) 99 | } 100 | } 101 | 102 | f(doc) 103 | return ls, nil 104 | } 105 | -------------------------------------------------------------------------------- /pkg/parser/html/search_test.go: -------------------------------------------------------------------------------- 1 | package html 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | const h = ` 9 | 10 | 11 | Document 12 | 15 | 16 | 17 | 18 | 19 | in-html test zznq 20 | 21 | 22 |
23 | in-html test zznq-div 24 |
25 | 26 | 29 | 30 |

31 | 32 |

33 | 34 | 35 | 36 | 37 | 38 | 39 | ` 40 | 41 | func TestMarkLocation(t *testing.T) { 42 | t.Log(MarkReflexLocation("zznq", strings.NewReader(h))) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/parser/javascript/variable.go: -------------------------------------------------------------------------------- 1 | package javascript 2 | 3 | import ( 4 | "github.com/tdewolff/parse/v2" 5 | "github.com/tdewolff/parse/v2/js" 6 | ) 7 | 8 | type varWalker struct { 9 | vars []string 10 | } 11 | 12 | var _ js.IVisitor = &varWalker{} 13 | 14 | func newVarWalker() *varWalker { 15 | return &varWalker{ 16 | vars: make([]string, 0), 17 | } 18 | } 19 | 20 | func (w *varWalker) Enter(n js.INode) js.IVisitor { 21 | switch n := n.(type) { 22 | case *js.VarDecl: 23 | for i := range n.List { 24 | v, ok := n.List[i].Binding.(*js.Var) 25 | if !ok { 26 | continue 27 | } 28 | w.vars = append(w.vars, string(v.Data)) 29 | } 30 | } 31 | 32 | return w 33 | } 34 | 35 | func (w *varWalker) Exit(n js.INode) {} 36 | 37 | func GetAllVariable(code string) ([]string, error) { 38 | ast, err := js.Parse(parse.NewInputString(code), js.Options{}) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | walker := &varWalker{} 44 | js.Walk(walker, ast) 45 | 46 | return walker.vars, nil 47 | } 48 | -------------------------------------------------------------------------------- /pkg/parser/javascript/variable_test.go: -------------------------------------------------------------------------------- 1 | package javascript 2 | 3 | import "testing" 4 | 5 | func TestGetAllVariable(t *testing.T) { 6 | t.Log(GetAllVariable(` 7 | var a = 1; 8 | var aa,bb,cc; 9 | let b = '1'; 10 | let d = taest(); 11 | 12 | const c = ''; 13 | console.log(a,b,c); 14 | `)) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/proxy/ca.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "crypto/x509" 7 | _ "embed" 8 | "io" 9 | "net/http" 10 | 11 | "github.com/elazarl/goproxy" 12 | ) 13 | 14 | var ( 15 | //go:embed xssfinder.ca.cert 16 | caCert []byte 17 | //go:embed xssfinder.ca.key 18 | caKey []byte 19 | ) 20 | 21 | func SetCA(caCert, caKey []byte) error { 22 | goproxyCa, err := tls.X509KeyPair(caCert, caKey) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | if goproxyCa.Leaf, err = x509.ParseCertificate(goproxyCa.Certificate[0]); err != nil { 28 | return err 29 | } 30 | 31 | goproxy.GoproxyCa = goproxyCa 32 | goproxy.OkConnect = &goproxy.ConnectAction{ 33 | Action: goproxy.ConnectAccept, 34 | TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 35 | goproxy.MitmConnect = &goproxy.ConnectAction{ 36 | Action: goproxy.ConnectMitm, 37 | TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 38 | goproxy.HTTPMitmConnect = &goproxy.ConnectAction{ 39 | Action: goproxy.ConnectHTTPMitm, 40 | TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 41 | goproxy.RejectConnect = &goproxy.ConnectAction{ 42 | Action: goproxy.ConnectReject, 43 | TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 44 | return nil 45 | } 46 | 47 | func DownloadCaHandlerFunc(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 48 | ctx.Logf("onRequest DownloadCaHanldeerFunc %s", req.URL.String()) 49 | 50 | resp := &http.Response{ 51 | StatusCode: http.StatusOK, 52 | ProtoMajor: 1, 53 | ProtoMinor: 1, 54 | Header: make(http.Header), 55 | ContentLength: int64(len(caCert)), 56 | Body: io.NopCloser(bytes.NewBuffer(caCert)), 57 | } 58 | resp.Header.Set("Content-Type", "application/x-x509-ca-cert") 59 | resp.Header.Set("Connection", "close") 60 | resp.Header.Set("Content-disposition", "attachment;filename=xxsfinder.cert") 61 | return req, resp 62 | } 63 | -------------------------------------------------------------------------------- /pkg/proxy/cert/gen.go: -------------------------------------------------------------------------------- 1 | package cert 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/pem" 10 | "fmt" 11 | "math/big" 12 | "time" 13 | ) 14 | 15 | func GenCA(identifier string) ([]byte, []byte, error) { 16 | // serialNumber 是 CA 颁布的唯一序列号 17 | serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) 18 | if err != nil { 19 | return nil, nil, err 20 | } 21 | 22 | tmpl := &x509.Certificate{ 23 | SerialNumber: serialNumber, 24 | Subject: pkix.Name{ 25 | CommonName: fmt.Sprintf("%s CA", identifier), 26 | Country: []string{identifier}, 27 | Organization: []string{identifier}, 28 | OrganizationalUnit: []string{identifier}, 29 | }, 30 | NotBefore: time.Now().AddDate(0, -1, 0), 31 | NotAfter: time.Now().AddDate(99, 0, 0), 32 | BasicConstraintsValid: true, 33 | IsCA: true, 34 | MaxPathLen: 2, 35 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 36 | KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, 37 | EmailAddresses: []string{fmt.Sprintf("x@%s.ca", identifier)}, 38 | } 39 | 40 | pk, err := rsa.GenerateKey(rand.Reader, 2048) 41 | if err != nil { 42 | return nil, nil, err 43 | } 44 | 45 | derBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk) 46 | if err != nil { 47 | return nil, nil, err 48 | } 49 | 50 | caKey := bytes.NewBuffer([]byte{}) 51 | if err = pem.Encode(caKey, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pk)}); err != nil { 52 | return nil, nil, err 53 | } 54 | 55 | caCert := bytes.NewBuffer([]byte{}) 56 | if err = pem.Encode(caCert, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { 57 | return nil, nil, err 58 | } 59 | return caCert.Bytes(), caKey.Bytes(), err 60 | } 61 | -------------------------------------------------------------------------------- /pkg/proxy/cert/gen_test.go: -------------------------------------------------------------------------------- 1 | package cert 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestGenCA(t *testing.T) { 9 | cert, key, err := GenCA("xssfinder") 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | t.Log(string(cert)) 14 | t.Log(string(key)) 15 | 16 | f, err := os.Create("xssfinder.ca.cert") 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | defer f.Close() 21 | f.Write(cert) 22 | 23 | f1, err := os.Create("xssfinder.ca.key") 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | defer f.Close() 28 | f1.Write(key) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/proxy/mitm.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "net/url" 7 | "strings" 8 | "sync" 9 | 10 | "github.com/Buzz2d0/xssfinder/pkg/httpdump" 11 | "github.com/elazarl/goproxy" 12 | "github.com/gokitx/pkgs/slicex" 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | func init() { 17 | if err := SetCA(caCert, caKey); err != nil { 18 | panic(err) 19 | } 20 | } 21 | 22 | type LogrusLogger struct { 23 | verbose bool 24 | } 25 | 26 | func (l LogrusLogger) Printf(format string, v ...interface{}) { 27 | if l.verbose { 28 | // TODO 先忽略此日志 29 | // logrus.Tracef(format, v...) 30 | } 31 | } 32 | 33 | type Config struct { 34 | Addr string 35 | Verbose bool // porxy 代理日志 36 | TargetHosts []string // 指定目标主机,将忽略其他主机;默认为所有 37 | ParentProxy string // 请求的代理 38 | CaHost string 39 | } 40 | 41 | type MitmServer struct { 42 | addr string 43 | cahost string 44 | reqs sync.Map 45 | goProxy *goproxy.ProxyHttpServer 46 | srv *http.Server 47 | 48 | C <-chan httpdump.Request 49 | } 50 | 51 | func NewMitmServer(conf Config) *MitmServer { 52 | proxy := goproxy.NewProxyHttpServer() 53 | proxy.Verbose = conf.Verbose 54 | proxy.Logger = LogrusLogger{verbose: conf.Verbose} 55 | 56 | if conf.CaHost == "" { 57 | conf.CaHost = "xssfinder.ca" 58 | } 59 | proxy.OnRequest(goproxy.DstHostIs(conf.CaHost)). 60 | DoFunc(DownloadCaHandlerFunc) 61 | proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) 62 | c := make(chan httpdump.Request, 5e1) 63 | mitm := &MitmServer{ 64 | addr: conf.Addr, 65 | cahost: conf.CaHost, 66 | goProxy: proxy, 67 | C: c, 68 | } 69 | 70 | if len(conf.ParentProxy) != 0 { 71 | mitm.SetParentProxy(conf.ParentProxy) 72 | } 73 | 74 | var targetFilte goproxy.ReqConditionFunc 75 | if len(conf.TargetHosts) != 0 { 76 | targetFilte = goproxy.ReqConditionFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) bool { 77 | return slicex.ContainsIn(conf.TargetHosts, req.Host, 78 | func(v, sub string) bool { 79 | return strings.Contains(sub, v) || strings.Contains(v, sub) 80 | }) 81 | }) 82 | proxy.OnRequest(targetFilte).DoFunc(mitm.OnRequest) 83 | } else { 84 | proxy.OnRequest().DoFunc(mitm.OnRequest) 85 | } 86 | 87 | proxy.OnResponse().DoFunc(mitm.MakeOnResponse(c)) 88 | return mitm 89 | } 90 | 91 | func (m *MitmServer) SetParentProxy(parentProxy string) { 92 | m.goProxy.Tr.Proxy = func(req *http.Request) (*url.URL, error) { 93 | return url.Parse(parentProxy) 94 | } 95 | } 96 | 97 | func (m *MitmServer) OnRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 98 | // ignore requests for static resources 99 | if ignoreRequestWithPath(req.URL.Path) { 100 | return req, nil 101 | } 102 | m.reqs.Store(ctx.Session, httpdump.MakeRequest(req)) 103 | return req, nil 104 | } 105 | 106 | func (m *MitmServer) MakeOnResponse(c chan httpdump.Request) func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { 107 | return func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { 108 | if resp == nil { 109 | m.reqs.Delete(ctx.Session) 110 | return resp 111 | } 112 | 113 | contentType := resp.Header.Get("Content-Type") 114 | if strings.Contains(contentType, "text/html") || 115 | strings.Contains(contentType, "text/htm") { 116 | if req, ok := m.reqs.LoadAndDelete(ctx.Session); ok { 117 | if request, ok := req.(httpdump.Request); ok { 118 | logrus.Debugln("[mitm] received:", request.URL) 119 | request.Response = httpdump.MakeResponse(request, resp) 120 | c <- request 121 | } 122 | } 123 | } else { 124 | m.reqs.Delete(ctx.Session) 125 | } 126 | 127 | return resp 128 | } 129 | } 130 | 131 | func (m *MitmServer) ListenAndServe() error { 132 | serv := &http.Server{ 133 | Addr: m.addr, 134 | Handler: m.goProxy, 135 | } 136 | m.srv = serv 137 | logrus.Infoln("[mitm] listen at: ", m.addr) 138 | logrus.Infoln("[mitm] ca: ", "http://"+m.cahost) 139 | return serv.ListenAndServe() 140 | } 141 | 142 | func (m *MitmServer) Shutdown(ctx context.Context) error { 143 | return m.srv.Shutdown(ctx) 144 | } 145 | -------------------------------------------------------------------------------- /pkg/proxy/util.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "path/filepath" 5 | 6 | "github.com/gokitx/pkgs/slicex" 7 | ) 8 | 9 | var ( 10 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types 11 | exts = []string{ 12 | ".aac", 13 | ".abw", 14 | ".arc", 15 | ".avif", 16 | ".avi", 17 | ".azw", 18 | ".bin", 19 | ".bmp", 20 | ".bz", 21 | ".bz2", 22 | ".cda", 23 | ".csh", 24 | ".css", 25 | ".csv", 26 | ".doc", 27 | ".docx", 28 | ".eot", 29 | ".epub", 30 | ".gz", 31 | ".gif", 32 | ".ico", 33 | ".ics", 34 | ".jpeg", 35 | ".jpg", 36 | ".js", 37 | ".json", 38 | ".jsonld", 39 | ".mid", 40 | ".midi", 41 | ".mjs", 42 | ".mp3", 43 | ".mp4", 44 | ".mpeg", 45 | ".mpkg", 46 | ".odp", 47 | ".ods", 48 | ".odt", 49 | ".oga", 50 | ".ogv", 51 | ".ogx", 52 | ".opus", 53 | ".otf", 54 | ".png", 55 | ".pdf", 56 | ".ppt", 57 | ".pptx", 58 | ".rar", 59 | ".rtf", 60 | ".sh", 61 | ".svg", 62 | ".swf", 63 | ".tar", 64 | ".tif ", 65 | ".tiff", 66 | ".ts", 67 | ".ttf", 68 | ".txt", 69 | ".vsd", 70 | ".wav", 71 | ".weba", 72 | ".webm", 73 | ".webp", 74 | ".woff", 75 | ".woff2", 76 | ".xls", 77 | ".xlsx", 78 | ".xml", 79 | ".xul", 80 | ".zip", 81 | ".3gp", 82 | ".3g2", 83 | ".7z", 84 | } 85 | ) 86 | 87 | func ignoreRequestWithPath(path string) bool { 88 | return slicex.ContainsIn(exts, 89 | filepath.Ext(path)) 90 | } 91 | -------------------------------------------------------------------------------- /pkg/proxy/util_test.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "net/url" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | func TestURL(t *testing.T) { 10 | URL, _ := url.Parse("http://foo.com/static/ab.css") 11 | t.Log(URL.Path, filepath.Ext(URL.Path), ignoreRequestWithPath(URL.Path)) 12 | URL, _ = url.Parse("http://foo.com/static/ab.js") 13 | t.Log(URL.Path, filepath.Ext(URL.Path), ignoreRequestWithPath(URL.Path)) 14 | URL, _ = url.Parse("http://foo.com/static/ab.png") 15 | t.Log(URL.Path, filepath.Ext(URL.Path), ignoreRequestWithPath(URL.Path)) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/proxy/xssfinder.ca.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDuDCCAqCgAwIBAgIQN+N8rK5Yya1FTDeKuwILoTANBgkqhkiG9w0BAQsFADBT 3 | MRIwEAYDVQQGEwlYU1NGaW5kZXIxEjAQBgNVBAoTCVhTU0ZpbmRlcjESMBAGA1UE 4 | CxMJeHNzZmluZGVyMRUwEwYDVQQDEwxYU1NGaW5kZXIgQ0EwIBcNMjIwMzE0MDg0 5 | MjI4WhgPMjEyMTA0MTQwODQyMjhaMFMxEjAQBgNVBAYTCVhTU0ZpbmRlcjESMBAG 6 | A1UEChMJWFNTRmluZGVyMRIwEAYDVQQLEwl4c3NmaW5kZXIxFTATBgNVBAMTDFhT 7 | U0ZpbmRlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf86moh 8 | +RykmO6fuYWN7FemdlCI+q39IYwa4T8YDKaMqfmad6cdNDkjI/BcWkh97eVi8SFG 9 | 4dB95NQRnniuvTx4B1Sza34K6srDb9WJqom2VSpC2XzhhZ5fnWSsQ455FrkcacYg 10 | W+1CzftabGvFsH7YVOqzxiW6hwTnvcyXZpibBE3D+lwP0QFRoHi/FgNfKj5MflGK 11 | kRDZ3xXnbLcR2LUkYKz/GMThOCxgjZrv/w94sbeetVoqC0EJDrtOdt5xZ11PWdTX 12 | AMz8vRFKvHh+D4ZLUhTjf07mPj84OiR0U4Oklvad9icf2UqRgn4fUSulB8kUyOXI 13 | SWb8cZDkMeVazXMCAwEAAaOBhTCBgjAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0lBBYw 14 | FAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQIwHQYDVR0O 15 | BBYEFOMnO0NiJA0NerIfSreKCB9VKe5rMB4GA1UdEQQXMBWBE2FkbWluQHhzc2Zp 16 | bmRlci5vcmcwDQYJKoZIhvcNAQELBQADggEBALFfxj2a7QO/5aVsVF2vwfB5t1xV 17 | u3whp/RSpuMHx99p1+LNEjHgLsI/qGURu3BFmchWeG6fvpvNEorYOyljDCMQTuus 18 | vgnHQ797WHkPXB9d+c6s3ajnT1MDi4wYwtTx/ECU30kt1WpHz5/s0t5qkj1Ob4sk 19 | qMmaCBkE7qR0xPYTxmxyo4q+w32zTijsglJ9N3B3tw7jJ4LYYIQtzsxGZPDE2Sax 20 | wMc8bmh+ENxpacTs1Z7X14jPVS81WNvzgbo/UMB4YLDufqzOcNDKdllHJLx3etNU 21 | X3DDwnh3eqdm5vE/+2z4BSXLgg6DTtcq2i4mbOJdyPE+H8JOSlKq6qsfXsM= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /pkg/proxy/xssfinder.ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAt/zqaiH5HKSY7p+5hY3sV6Z2UIj6rf0hjBrhPxgMpoyp+Zp3 3 | px00OSMj8FxaSH3t5WLxIUbh0H3k1BGeeK69PHgHVLNrfgrqysNv1YmqibZVKkLZ 4 | fOGFnl+dZKxDjnkWuRxpxiBb7ULN+1psa8WwfthU6rPGJbqHBOe9zJdmmJsETcP6 5 | XA/RAVGgeL8WA18qPkx+UYqRENnfFedstxHYtSRgrP8YxOE4LGCNmu//D3ixt561 6 | WioLQQkOu0523nFnXU9Z1NcAzPy9EUq8eH4PhktSFON/TuY+Pzg6JHRTg6SW9p32 7 | Jx/ZSpGCfh9RK6UHyRTI5chJZvxxkOQx5VrNcwIDAQABAoIBAGP2dwo48Rb92tVz 8 | VKHabTlmCMxS0Bgt+scbZ5wXHfBzZnpl0XKrbE9K52DVXHwcZ1+E9UvGLjRP4j7S 9 | X85j6g7ri9gaL2Ho3o5RxvY2+MAfn3/5mB7AjQ9yFZq3+XB0G8NxS4rI1AlBjZZu 10 | F5AqdW1FbkWibRrckDU7D6UT8TshufjhnOoCVB08EXXNr3YeAH6NjD5fc5pqrU7p 11 | OzixMDOQfOGTDgztHWV4SmWLcrjU2/l19IxkGiDT3pgXLBLAWRSjqkhNB7IRm94z 12 | ld1gb0yZJfj6OdKONPb2R8zndzTwkZXFZ2n827nW39uXGur2w1+MowfBtP8Ql+sL 13 | J+BJiwkCgYEAzdpP+tFTGH//u2cTJK1FrB79yJ9+6ktGIgeLShGS/u/Ug2I+76xg 14 | LI2XIRjZakHoHUQbxz6QOn25dRHZNAr4I2fYN3VNp+K9iRE8IzmiS46jUD6IUqiE 15 | d2kJIF2eDXOuXNBM7IxHxRQwWh79g2TE/5uKpHd5ihHPp7HshLI/xrUCgYEA5M8J 16 | hZDh7uXJOQXbrCx/SwARf7z2JcUQi3GSjW1KnnctewYtPJKNsl8gC4sRspZlEeDu 17 | MQ/iYi/7UhyB9sDkiuuWtFCos4DUsI+1iI0zi64IFbkMx82SfVuV0CAJU4LcQwE7 18 | kWu1BHDMQZFb3QSvtyYc2VyJbGjXaYaZgCdTdIcCgYEAmFrg4Rk2MCbkJlaGg5m+ 19 | vEJ/wJHThCLMOHlu7RYOvqLQnaEERa1FzRPKbBORLqSy354+FPxIERJmRbA9GtiX 20 | iNTd4UXvgLc/YoggA04vwU5DrytpCAxzZlGWEJ9ZUElzHnyIwnac0886bikLpsjZ 21 | SPlc5pcKnFbMYyg7jobCzxUCgYBxZ3QOsSpp35YGHzMreLp2j8gcialuOufiVR4P 22 | 3pVXSTZPfVY6aWC1gZQ+ZGUXIJdjbxpfUsIfbCyiG+MGS8B6Yh3FrRyQK1A/Wlrd 23 | HS7pEg6LxbXh6a+rVhNAnkBBFeDZjOjbQIjtdIKoFMV1G/eDTO6LhOPPyX7KnJ3D 24 | +LnSTQKBgFQLhHwAWU52NMvvjMTu5nEOQket3snXUBmDS5xcJrxs7utZv344m94f 25 | ih2n532uASOSSzVbwpdf6Z+iXM6Aihw429BsfKAuwqLy4iDdRUOf/9ErhZ5QZKqF 26 | EKTK0RRmWiAnKE74OHANJsSnHsWhrQlOM2pQ5PUBM/NSIV3pU8B7 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/demo/dom_eval.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | demo01 9 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/demo/dom_hookconvt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Click%20me 17 | 18 | 19 | 20 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/notifier-temp.yaml: -------------------------------------------------------------------------------- 1 | dingbot: 2 | token: xxx 3 | secret: xxxxxx --------------------------------------------------------------------------------