first commit

main
root 2023-06-21 21:15:51 +00:00
commit ae1ec051ec
13 changed files with 27490 additions and 0 deletions

72
README.md 100644
View File

@ -0,0 +1,72 @@
# Pyng
Network subnet live scanner and port scanner for URL or IP.
[![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/) [![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg)](https://opensource.org/licenses/) [![AGPL License](https://img.shields.io/badge/license-AGPL-blue.svg)](http://www.gnu.org/licenses/agpl-3.0)
## Installation
System require only to have a python3 and pip3 package
```bash
apt install git python3.9 python3-pip iperf3
```
Install the python3 packages
```bash
pip3 install requests scapy simplepam flask_fontawesome flask_navigation flask_socketio iperf3
```
You can also use the requirements file in this git repo.
```bash
pip3 install -r requirements.txt
```
Then pull the git poject.
## Run
Go to the **Pyng** folder and run :
```bash
python3 app.py
```
## Optimizations
!! Take care that by default, to be use in production, the parameter allow_unsafe_werkzeug is set to True !!
You can change the used port (by default 5006) in this last line in the python app.py
```python
if __name__ == '__main__':
socketio.run(app, host='0.0.0.0', port='5006', allow_unsafe_werkzeug=True)
```
You can also run it using systemctl, just create a **pyng.service** in /etc/systemd/system/ with these infos :
```bash
[Unit]
Description= Pyng service
After=multi-user.target
[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/python3 </path/to/Pyng/app.py>
[Install]
WantedBy=multi-user.target
```
Then reload systemctl, enable it (if you want it on server start) and start it:
```bash
systemctl daemon-reload
systemctl enable pyng.service
systemctl start pyng.service
```

Binary file not shown.

185
app.py 100644
View File

@ -0,0 +1,185 @@
import re
import os
import sys
import time
import socket
import iperf3
import logging
import requests
import subprocess
import scapy.all as scapy
from simplepam import authenticate
from flask_fontawesome import FontAwesome
from flask_navigation import Navigation
from flask_socketio import SocketIO, emit
from flask import Flask, render_template, Response, request, redirect, url_for, session, escape
#Global config
__version__ = "1.0.0"
path = os.path.dirname(__file__)
logging.basicConfig(filename=path+'/log/Pyng.log', level=logging.DEBUG, format=f'%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s')
logging.info('PROGRAM START')
#Init
app = Flask(__name__)
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
socketio = SocketIO(app,cors_allowed_origins="*",async_mode="threading")
fa = FontAwesome(app)
nav = Navigation(app)
#Variable
uphost=0
#Menu
nav.Bar('top', [
nav.Item('Network', 'index'),
nav.Item('Port', 'port'),
nav.Item('Tools','tools'),
])
##Check if URL or IP
def check_url_ip(target):
ip_pattern = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
if ip_pattern.match(target):
ip_target = target
else:
if target.startswith("http://"):
target = target[7:]
elif target.startswith("https://"):
target = target[8:]
try:
socket.gethostbyname(target)
ip_target = socket.gethostbyname(target)
except:
ip_target='0.0.0.0'
return ip_target
def iperf():
client = iperf3.Client()
client.duration = 2
client.server_hostname = 'ping.online.net'
client.port = 5200
try:
result = client.run()
send_speed = round(result.sent_Mbps,2)
received_speed = round(result.received_Mbps,2)
except:
send_speed = "Could not be tested"
received_speed = "Could not be tested"
return send_speed, received_speed
##Check Mac adress
def get_mac_details(mac_address):
##Sleep to avoid Free API Limitation
time.sleep(.65)
url = "https://api.macvendors.com/"
response = requests.get(url+mac_address)
if response.status_code != 200:
retour = " "
else:
retour = response.content.decode()
return retour
##Check DNS resolution
def dns_resolv(target):
try:
socket.gethostbyaddr(target)
dns=socket.gethostbyaddr(target)[0]
except:
dns=""
return dns
#Background port scan
def bg_scan_port(target):
for port in range(0, 65535):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = sock.connect_ex((target, port))
if result == 0:
socketio.emit('scan_response',{'data': 'Port: ' + format(port)})
sock.close()
#Background network scan
def bg_scan_network(subnet):
global uphost
uphost=0
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
request = scapy.ARP()
request.pdst = subnet
broadcast = scapy.Ether()
broadcast.dst = 'ff:ff:ff:ff:ff:ff'
request_broadcast = broadcast / request
clients = scapy.srp(request_broadcast, timeout = 1)[0]
for element in clients:
uphost=uphost+1
macdet=get_mac_details(element[1].hwsrc)
dns=dns_resolv(element[1].psrc)
socketio.emit('net_response',{'ip': element[1].psrc,'mac': element[1].hwsrc,'vendor':macdet,'dns':dns})
sock.close()
#Main route to index.
@app.route("/")
def index():
app.logger.info('Info level log')
app.logger.warning('Warning level log')
if 'username' in session:
return render_template('index.html')
return render_template('login.html',title="Welcome")
@app.route("/tools", methods=['GET', 'POST'])
def tools():
resultat =''
send,received=iperf();
resultat = '<p>Send : '+str(send)+' Mbp/s</p><p>Received : '+str(received)+' Mbp/s</p>'
return render_template('tool.html',resultat=resultat,title="Tools")
@app.route("/port")
def port():
app.logger.info('Info level log')
app.logger.warning('Warning level log')
if 'username' in session:
return render_template('port.html')
return render_template('login.html',title="Welcome")
##Socket pour le scan de port
@socketio.on('scan_port')
def connect(data):
target=check_url_ip(data)
if target == '0.0.0.0':
emit('scan_response', {'data': 'Server '+format(data)+' not found'})
else:
emit('scan_response', {'data': 'Scan on progress for '+format(data)+'('+target+')'})
socketio.start_background_task(bg_scan_port(target))
emit('scan_response', {'data': 'Scan Complete'})
##Socket pour le scan réseau
@socketio.on('net_port')
def connect(data):
global uphost
emit('net_response', {'ip':'IP','mac':'Mac address','vendor':'Vendor resolution','dns':'Local DNS'})
socketio.start_background_task(bg_scan_network(data))
downhost=256-uphost
emit('net_response', {'ip':' ','mac':'Up:'+str(uphost),'vendor': 'Down:'+str(downhost),'dns':' '})
##Login
@app.route("/login", methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if authenticate(str(username), str(password)):
session['username'] = request.form['username']
logging.info('AUTH LOG - New auth from '+username+' !')
return redirect(url_for('index'))
else:
resultats='<p id="alert" style="color:red">Invalid Username/Password ! </p>'
return render_template('login.html', alertmessage=resultats)
##Logout
@app.route('/logout')
def logout():
session.pop('username', None)
return redirect(url_for('index'))
##Start
if __name__ == '__main__':
socketio.run(app, host='0.0.0.0', port='5005', allow_unsafe_werkzeug=True)

187
app.py.ori 100644
View File

@ -0,0 +1,187 @@
import re
import os
import sys
import time
import socket
import iperf3
import logging
import requests
import subprocess
import scapy.all as scapy
from simplepam import authenticate
from flask_fontawesome import FontAwesome
from flask_navigation import Navigation
from flask_socketio import SocketIO, emit
from flask import Flask, render_template, Response, request, redirect, url_for, session, escape
#Global config
__version__ = "1.0.0"
path = os.path.dirname(__file__)
logging.basicConfig(filename=path+'/log/Pyng.log', level=logging.DEBUG, format=f'%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s')
logging.info('PROGRAM START')
#Init
app = Flask(__name__)
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
socketio = SocketIO(app,cors_allowed_origins="*",async_mode="threading")
fa = FontAwesome(app)
nav = Navigation(app)
#Variable
uphost=0
#Menu
nav.Bar('top', [
nav.Item('Network', 'index'),
nav.Item('Port', 'port'),
nav.Item('Tools','tools'),
])
##Check if URL or IP
def check_url_ip(target):
ip_pattern = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
if ip_pattern.match(target):
ip_target = target
else:
if target.startswith("http://"):
target = target[7:]
elif target.startswith("https://"):
target = target[8:]
try:
socket.gethostbyname(target)
ip_target = socket.gethostbyname(target)
except:
ip_target='0.0.0.0'
return ip_target
def iperf():
client = iperf3.Client()
client.duration = 2
client.server_hostname = 'ping.online.net'
client.port = 5200
try:
result = client.run()
send_speed = round(result.sent_Mbps,2)
received_speed = round(result.received_Mbps,2)
except:
send_speed = "Could not be tested"
received_speed = "Could not be tested"
return send_speed, received_speed
##Check Mac adress
def get_mac_details(mac_address):
##Sleep to avoid Free API Limitation
time.sleep(.65)
url = "https://api.macvendors.com/"
response = requests.get(url+mac_address)
#DEBUG But too fast :/
# print("DEBUG : MAC"+str(mac_address)+" CODE:"+str(response.status_code)+" RESULTAT : "+str(response.content.decode()))
if response.status_code != 200:
retour = " "
else:
retour = response.content.decode()
return retour
##Check DNS resolution
def dns_resolv(target):
try:
socket.gethostbyaddr(target)
dns=socket.gethostbyaddr(target)[0]
except:
dns=""
return dns
#Background port scan
def bg_scan_port(target):
for port in range(0, 65535):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = sock.connect_ex((target, port))
if result == 0:
socketio.emit('scan_response',{'data': 'Port: ' + format(port)})
sock.close()
#Background network scan
def bg_scan_network(subnet):
global uphost
uphost=0
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
request = scapy.ARP()
request.pdst = subnet
broadcast = scapy.Ether()
broadcast.dst = 'ff:ff:ff:ff:ff:ff'
request_broadcast = broadcast / request
clients = scapy.srp(request_broadcast, timeout = 1)[0]
for element in clients:
uphost=uphost+1
macdet=get_mac_details(element[1].hwsrc)
dns=dns_resolv(element[1].psrc)
socketio.emit('net_response',{'ip': element[1].psrc,'mac': element[1].hwsrc,'vendor':macdet,'dns':dns})
sock.close()
#Main route to index.
@app.route("/")
def index():
app.logger.info('Info level log')
app.logger.warning('Warning level log')
if 'username' in session:
return render_template('index.html')
return render_template('login.html',title="Welcome")
@app.route("/tools", methods=['GET', 'POST'])
def tools():
resultat =''
send,received=iperf();
resultat = '<p>Send : '+str(send)+' Mbp/s</p><p>Received : '+str(received)+' Mbp/s</p>'
return render_template('tool.html',resultat=resultat,title="Tools")
@app.route("/port")
def port():
app.logger.info('Info level log')
app.logger.warning('Warning level log')
if 'username' in session:
return render_template('port.html')
return render_template('login.html',title="Welcome")
##Socket pour le scan de port
@socketio.on('scan_port')
def connect(data):
target=check_url_ip(data)
if target == '0.0.0.0':
emit('scan_response', {'data': 'Server '+format(data)+' not found'})
else:
emit('scan_response', {'data': 'Scan on progress for '+format(data)+'('+target+')'})
socketio.start_background_task(bg_scan_port(target))
emit('scan_response', {'data': 'Scan Complete'})
##Socket pour le scan réseau
@socketio.on('net_port')
def connect(data):
global uphost
emit('net_response', {'ip':'IP','mac':'Mac address','vendor':'Vendor resolution','dns':'Local DNS'})
socketio.start_background_task(bg_scan_network(data))
downhost=256-uphost
emit('net_response', {'ip':' ','mac':'Up:'+str(uphost),'vendor': 'Down:'+str(downhost),'dns':' '})
##Login
@app.route("/login", methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if authenticate(str(username), str(password)):
session['username'] = request.form['username']
logging.info('AUTH LOG - New auth from '+username+' !')
return redirect(url_for('index'))
else:
resultats='<p id="alert" style="color:red">Invalid Username/Password ! </p>'
return render_template('login.html', alertmessage=resultats)
##Logout
@app.route('/logout')
def logout():
session.pop('username', None)
return redirect(url_for('index'))
##Start
if __name__ == '__main__':
socketio.run(app, host='0.0.0.0', port='5005', allow_unsafe_werkzeug=True)

17630
log/Pfing.log 100644

File diff suppressed because it is too large Load Diff

9093
log/Pyng.log 100644

File diff suppressed because it is too large Load Diff

9
pyng.service 100644
View File

@ -0,0 +1,9 @@
[Unit]
Description= Pyng service
After=multi-user.target
[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/python3 /data/Pyng/app.py
[Install]
WantedBy=multi-user.target

7
requirements.txt 100644
View File

@ -0,0 +1,7 @@
requests
scapy
simplepam
flask_fontawesome
flask_navigation
flask_socketio
iperf3

122
static/style.css 100644
View File

@ -0,0 +1,122 @@
body {
font-size: 13px;
font-weight: 300;
font-family: helvetica, arial, verdana, sans-serif;
text-align:center;
min-height: 100% !important;
background: #333;
color: white;
}
::placeholder {
color:#D7D7D7;
}
.login {
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
width:300px;
height:400px;
padding:50px;
text-align:center;
margin:auto;
background-color:white;
}
button {
display: flex;
flex-direction: row;
align-items: center;
border-radius: 6px;
border: none;
width:100px;
background: Transparent;
box-shadow: 0px 0.5px 1px rgba(0, 0, 0, 0.1), inset 0px 0.5px 0.5px rgba(255, 255, 255, 0.5), 0px 0px 0px 0.5px rgba(0, 0, 0, 0.12);
cursor: pointer;
margin:auto;
padding: 5px 5px;
}
.space {
display: flex;
justify-content: space-between;
width: 100%;
margin: 0 auto;
padding: 20px 0;
}
span {
display: flex;
justify-content: space-between;
width: 100%;
margin: 0 auto;
padding: 5px 0;
}
.logout {
text-align: right;
width:100%;
text-decoration:none;
height:33px;
}
.logout a {
text-decoration: none;
}
.logout ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #454545;
position: -webkit-sticky; /* Safari */
position: sticky;
top: 0;
}
.logout li {
float: left;
}
.logout li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
.logout li a:hover {
background-color: #111;
}
.logout .active {
background-color: #BB8FCE;
}
#output {
border-radius: 15px;
box-shadow: 0px 0px 9px 0px rgba(0,0,0,0.1);
width: fit-content;
background-color:white;
color:black;
}
#output tr:nth-child(even){
background-color:#eeeeee;
}
td {
border-collapse: collapse;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 40px;
padding-right: 40px;
}
tr:not(:first-child):hover {
background-color: rgba(114, 114, 113, 0.2);
}
input {
padding: 6px;
font-size: 14px;
border-width: 0px;
border-color: #CCCCCC;
background-color: #FFFFFF;
color: #000000;
border-style: solid;
border-radius: 9px;
box-shadow: 0px 0px 5px rgba(66,66,66,.75);
}

View File

@ -0,0 +1,64 @@
<!DOCTYPE HTML>
<html>
<link href="{{url_for('static', filename = 'style.css')}}" rel="stylesheet">
{{ fontawesome_css() }}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.4/socket.io.js" integrity="sha512-aMGMvNYu8Ue4G+fHa359jcPb1u+ytAF+P2SCb+PxrjCdO3n3ZTxJ30zuH39rimUggmTwmh2u7wvQsDTHESnmfQ==" crossorigin="anonymous"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
var socket = io();
socket.on('connect', function() {
socket.emit('my_event', {data: 'I\'m connected!'});
});
socket.on('scan_response', function(msg) {
$('#output').append('<br>' + $('<div/>').text(msg.data).html());
})
$('form#scanner').submit(function(event) {
document.getElementById("output").innerHTML = "";
socket.emit('scan_port', $('#Target').val());
return false;
});
socket.on('net_response', function(msg) {
$('#output').append('<tr><td>' + $('<div/>').text(msg.ip).html()+'</td><td>'+ $('<div/>').text(msg.mac).html()+'</td><td>'+ $('<div/>').text(msg.vendor).html()+'</td><td>'+ $('<div/>').text(msg.dns).html()+'</td></tr>');
})
$('form#network').submit(function(event) {
document.getElementById("output").innerHTML = "";
socket.emit('net_port', $('#Subnet').val());
return false;
});
});
</script>
</head>
<body>
<title>Pyng</title>
<div class="logout">
<ul>
{% for item in nav.top %}
<li class="{{ 'active' if item.is_active else '' }}">
<a href="{{ item.url }}">{{ item.label }}</a>
</li>
{% endfor %}
<li style="float:right;">
<a href="logout"><span class="fas fa-user" style="display: inline;"></span> Logout</a>
</li>
</ul>
</div>
<h1>Network Scan</h1>
<form id="network" method="POST" action="#">
<input type="text" id="Subnet" placeholder="192.168.0.0/24">
<input type="submit" value="Send">
</form>
<center>
<br><br><div id="output"></div>
</center>
</body>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<link href="{{url_for('static', filename = 'style.css')}}" rel="stylesheet">
{{ fontawesome_css() }}
<div id="titre">Pyng</div>
<div class="space"></div>
<br>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" rel="stylesheet" type="text/c>
<link href='https://fonts.googleapis.com/css?family=Dosis' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Nunito' rel='stylesheet' type='text/css'>
<body>
<center>
<div class="space"></div>
<center>
<div class="login">
<h3>Login</h3>
<form action="/login" method="post">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<button name="Login" type="submit"><span class="fas fa-user" onclick="loading();"></span></button>
</form>
</div>
</center>
</html>

View File

@ -0,0 +1,63 @@
<!DOCTYPE HTML>
<html>
<link href="{{url_for('static', filename = 'style.css')}}" rel="stylesheet">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.4/socket.io.js" integrity="sha512-aMGMvNYu8Ue4G+fHa359jcPb1u+ytAF+P2SCb+PxrjCdO3n3ZTxJ30zuH39rimUggmTwmh2u7wvQsDTHESnmfQ==" crossorigin="anonymous"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
var socket = io();
socket.on('connect', function() {
socket.emit('my_event', {data: 'I\'m connected!'});
});
socket.on('scan_response', function(msg) {
$('#output').append('<tr><td>' + $('<div/>').text(msg.data).html()+'</td></tr>');
})
$('form#scanner').submit(function(event) {
document.getElementById("output").innerHTML = "";
socket.emit('scan_port', $('#Target').val());
return false;
});
socket.on('net_response', function(msg) {
$('#output').append('<br>' + $('<div/>').text(msg.data).html());
})
$('form#network').submit(function(event) {
document.getElementById("output").innerHTML = "";
socket.emit('net_port', $('#Subnet').val());
return false;
});
});
</script>
</head>
<body>
<title>Pyng</title>
<div class="logout">
<ul>
{% for item in nav.top %}
<li class="{{ 'active' if item.is_active else '' }}">
<a href="{{ item.url }}">{{ item.label }}</a>
</li>
{% endfor %}
<li style="float:right;">
<a href="logout"><span class="fas fa-user" style="display: inline;"></span> Logout</a>
</li>
</ul>
</div>
<h1>Port Scan</h1>
<form id="scanner" method="POST" action="#">
<input type="text" id="Target" placeholder="http://url.com or 192.168.1.1">
<input type="submit" value="Send">
</form>
<center>
<br><br><div id="output"></div>
</center>
</body>
</html>

View File

@ -0,0 +1,28 @@
<!DOCTYPE HTML>
<html>
<link href="{{url_for('static', filename = 'style.css')}}" rel="stylesheet">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
</head>
<body>
<title>Pyng</title>
<div class="logout">
<ul>
{% for item in nav.top %}
<li class="{{ 'active' if item.is_active else '' }}">
<a href="{{ item.url }}">{{ item.label }}</a>
</li>
{% endfor %}
<li style="float:right;">
<a href="logout"><span class="fas fa-user" style="display: inline;"></span> Logout</a>
</li>
</ul>
</div>
<h1>Tools</h1>
<center>
<br><h2>Speed test</h2><br>
{{ resultat | safe }}
</center>
</body>
</html>