ARPscanner first release (#271)

This commit is contained in:
Giuseppe De Marco
2017-10-04 10:29:17 +02:00
committed by Franco Fichtner
parent ef82e387a6
commit aec97c0b17
19 changed files with 1063 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
PLUGIN_NAME= ARP Scan
PLUGIN_VERSION= 0.1
PLUGIN_COMMENT= Get all peers connected to a LAN
PLUGIN_MAINTAINER= giuseppe.demarco@unical.it
PLUGIN_DEPENDS= arp-scan
PLUGINS_DEVEL= yes
.include "../../Mk/plugins.mk"
+16
View File
@@ -0,0 +1,16 @@
ARP Scan sends ARP packets to hosts on the local network and displays
any responses that are received.
The Address Resolution Protocol (ARP) uses a simple message format containing
one address resolution request or response (usually IPv4).
ARP Scan is a simple tool yet very powerful and it represent the only way
to find a device using an arp response. Once youve found the MAC
address, you can find more info about that device by matching that MAC
address to its vendor.
Arp scan techniques are very important to understand ARP/MAC responses
for penetration tester and diagnosys purpose. It is the first tool for
network monitoring, especially against ip collisions, arpspoof and all
kind of hijack techniques, like Man-In-The-Middle Attack. It also helps
in cases when someone is spoofing IP address and DoS-ing your server.
@@ -0,0 +1,91 @@
<?php
/**
* Copyright (C) 2017 Giuseppe De Marco <giuseppe.demarco@unical.it>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\ARPscanner\Api;
use \OPNsense\Base\ApiControllerBase;
use \OPNsense\Core\Backend;
class ServiceController extends ApiControllerBase
{
public function startAction()
{
if ($this->request->isPost()) {
$ifname = escapeshellarg($_POST['interface']);
$networks = escapeshellarg($_POST['networks']);
//~ return 'arpscanner start '.$ifname.' '.$networks;
$backend = new Backend();
$result = json_decode(trim($backend->configdRun('arpscanner start '.$ifname.' '.$networks)), true);
return $result;
}
return array("message" => "unable to run config action");
}
public function statusAction()
{
if ($this->request->isPost()) {
$ifname = escapeshellarg($_POST['interface']);
//~ return 'arpscanner start '.$ifname.' '.$networks;
$backend = new Backend();
$result = json_decode(trim($backend->configdRun('arpscanner status '.$ifname)), true);
return $result;
}
return array("message" => "this action must be called using the POST method");
}
public function stopAction()
{
if ($this->request->isPost()) {
$ifname = escapeshellarg($_POST['interface']);
$backend = new Backend();
$bckresult = trim($backend->configdRun("arpscanner stop ".$ifname));
if ($bckresult !== null) {
// only return valid json type responses
return $bckresult;
}
return array("message" => "error");
}
}
public function checkAction()
{
if ($this->request->isPost()) {
$ifname = escapeshellarg($_POST['interface']);
// test: "configctl arpscanner check em0"
$backend = new Backend();
$bckresult = json_decode(trim($backend->configdRun("arpscanner check ".$ifname)), true);
if ($bckresult !== null) {
// only return valid json type responses
return $bckresult;
}
return array("message" => "error");
}
}
}
@@ -0,0 +1,103 @@
<?php
/**
* Copyright (C) 2017 Giuseppe De Marco <giuseppe.demarco@unical.it>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
**/
namespace OPNsense\ARPscanner\Api;
use \OPNsense\Base\ApiControllerBase;
use \OPNsense\ARPscanner\ARPscanner;
use \OPNsense\Core\Config;
use \OPNsense\Core\Backend;
class SettingsController extends ApiControllerBase
{
/* retrieve general settings
* @return array general settings
*/
public function getAction()
{
// define list of configurable settings
$result = array();
if ($this->request->isGet()) {
$mdl = new ARPscanner();
$result['arpscanner'] = $mdl->getNodes();
// returns: {"arpscanner":{"general":{"interface":
// {"lan":{"value":"lan","selected":1}},"networks":
// {"10.0.1.0\/24":{"value":"10.0.1.0\/24","selected":1}}}}}
$backend = new Backend();
$bckresult = trim($backend->configdRun("arpscanner interfaces"));
$ifnames = json_decode($bckresult);
$result['arpscanner']['general']['interface'] = array();
if (is_array($ifnames) || is_object($ifnames))
{
foreach ($ifnames as &$arr) {
$ifname = $arr[0];
$result['arpscanner']['general']['interface'][$ifname] = array();
$result['arpscanner']['general']['interface'][$ifname]['value'] = join(", ", array($ifname, " (".$arr[2].")") );
}
}
// $result['arpscanner']['general']['networks'] = '192.168.1.0/24,172.16.45.0/25';
}
return $result;
}
/**
* update arpscan settings
* @return array status
*/
public function setAction()
{
$result = array("result"=>"failed");
if ($this->request->isPost()) {
// load model and update with provided data
$mdl = new ARPscanner();
$mdl->setNodes($this->request->getPost("arpscanner"));
// perform validation
$valMsgs = $mdl->performValidation();
foreach ($valMsgs as $field => $msg) {
if (!array_key_exists("validations", $result)) {
$result["validations"] = array();
}
$result["validations"]["general.".$msg->getField()] = $msg->getMessage();
}
// serialize model to config and save
if ($valMsgs->count() == 0) {
$mdl->serializeToConfig();
Config::getInstance()->save();
$result["result"] = "saved";
}
}
return $result;
}
}
@@ -0,0 +1,51 @@
<?php
/**
* Copyright (C) 2017 Giuseppe De Marco <giuseppe.demarco@unical.it>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OPNsense\ARPscanner;
/**
* Class IndexController
* @package OPNsense\ARPscanner
*/
class IndexController extends \OPNsense\Base\IndexController
{
/**
* ARP scanner index page
* @throws \Exception
*/
public function indexAction()
{
$this->view->title = gettext('ARP Scan');
$this->view->general = $this->getForm("general");
$this->view->pick('OPNsense/ARPscanner/index');
$this->view->generalForm = $this->getForm("general");
}
}
@@ -0,0 +1,15 @@
<form>
<field>
<id>arpscanner.general.interface</id>
<label>Listen Interface</label>
<type>dropdown</type>
<help>Interface where to find the networks logically linked to.</help>
</field>
<field>
<id>arpscanner.general.networks</id>
<label>Network to probe</label>
<type>text</type>
<help>Network to scan, default: localnet.
</help>
</field>
</form>
@@ -0,0 +1,9 @@
<acl>
<page-services-arpscanner>
<name>Diagnostics: ARP Scan</name>
<patterns>
<pattern>ui/arpscanner/*</pattern>
<pattern>api/arpscanner/*</pattern>
</patterns>
</page-services-arpscanner>
</acl>
@@ -0,0 +1,42 @@
<?php
/**
* Copyright (C) 2017 Giuseppe De Marco <giuseppe.demarco@unical.it>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OPNsense\ARPscanner;
use OPNsense\Base\BaseModel;
class ARPscanner extends BaseModel
{
public function test(){
$command="/sbin/ifconfig -l -u";
exec($command, $output);
return $output;
}
}
@@ -0,0 +1,19 @@
<model>
<mount>//OPNsense/ARPscanner</mount>
<version>1.0.0</version>
<description>ARP Scan</description>
<items>
<general>
<interface type="TextField">
<default>lan</default>
<Required>Y</Required>
</interface>
<networks type="TextField">
<default></default>
<Required>N</Required>
<ValidationMessage>Scan a different ipv4 network, instead of localnet</ValidationMessage>
<mask>/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}(,)?)+$/</mask>
</networks>
</general>
</items>
</model>
@@ -0,0 +1,7 @@
<menu>
<Interfaces>
<Diagnostics>
<ARPscanner VisibleName="ARP Scan" cssClass="fa fa-search fa-fw" url="/ui/arpscanner/"/>
</Diagnostics>
</Interfaces>
</menu>
@@ -0,0 +1,208 @@
{#
Copyright © 2017 Giuseppe De Marco <giuseppe.demarco@unical.it>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
#}
<script type="text/javascript">
function flush_table(){
$('#netTable tr').slice(2).remove()
}
var check_state = 0;
function check_scanner_status(ifname){
// sets 0 if stopped and 1 if running
sendData={'interface': ifname }
//~ // action to run after successful save, for example reconfigure service.
ajaxCall(url="/api/arpscanner/service/check", sendData, callback=function(data,status) {
// action to run after reload
if (data.length >= 1){
$("#update_stop").hide();
$("#update_start").show();
check_state = 1;
$("#scan_progress").addClass("fa fa-spinner fa-pulse");
$("#startScanner").addClass("disabled")
setTimeout(function(){
get_status(ifname)
}, 2000 );
} else {
$("#update_stop").show();
$("#update_start").hide();
check_state = 0;
$("#scan_progress").removeClass("fa fa-spinner fa-pulse");
$("#startScanner").removeClass("disabled")
}
});
}
function get_status(ifname){
flush_table();
ajaxCall(url="/api/arpscanner/service/status",
sendData={'interface':ifname},
callback=function(data,status) {
$.each(data['peers'], function(key_x,peer) {
//~ console.log(peer);
ip = peer[0];
mac = peer[1];
vendor = peer[2];
$('#netTable tr:last').after("<tr><td>"+ip+"</td><td>"+mac+"</td><td>"+vendor+"</td></tr>")
})
check_scanner_status(ifname)
$("#ifname").text(ifname);
$("#started").text(data['started']);
$("#last").text(data['last']);
});
}
$( document ).ready(function() {
var data_get_map = {'frm_GeneralSettings': "/api/arpscanner/settings/get"};
//~ console.log(data_get_map);
mapDataToFormUI(data_get_map).done(function(data){
// place actions to run after load, for example update form styles.
formatTokenizersUI();
$('select').selectpicker('refresh');
// check if the scanner is already running
first_status = $('#arpscanner\\.general\\.interface option:selected')[0].value;
check_scanner_status(first_status);
});
// link save button to API set action
$("#saveAct").click(function(){
saveFormToEndpoint(url="/api/arpscanner/settings/set",formid='frm_GeneralSettings',callback_ok=function(){
// action to run after successful save, for example reconfigure service.
ajaxCall(url="/api/arpscanner/service/reload", sendData={},callback=function(data,status) {
// action to run after reload
});
});
});
$("#statusScanner").click(function(){
value = $('#arpscanner\\.general\\.interface option:selected')[0].value;
get_status(value);
});
$("#stopScanner").click(function(){
// action to run after successful save, for example reconfigure service.
value = $('#arpscanner\\.general\\.interface option:selected')[0].value;
sendData={'interface': value }
ajaxCall(url="/api/arpscanner/service/stop", sendData, callback=function(data,status) {
// action to run after reload
//~ console.log(data);
$("#scan_progress").removeClass("fa fa-spinner fa-pulse");
check_scanner_status(value);
});
});
// CHECK STATUS
// check the status opf the scanner on selected interface
$("#arpscanner\\.general\\.interface").change(function(){
value = $('#arpscanner\\.general\\.interface option:selected')[0].value;
get_status(value);
$("#ifname").text(value);
$("#started").text('');
$("#last").text('');
});
$("#startScanner").click(function(){
//~ $("#responseMsg").removeClass("hidden");
$("#scan_progress").addClass("fa fa-spinner fa-pulse");
var ifname = $('#arpscanner\\.general\\.interface option:selected')[0].value;
var networks = $('#arpscanner\\.general\\.networks').val();
ajaxCall(url="/api/arpscanner/service/start",
sendData={'interface':ifname, 'networks': networks},
callback=function(data,status) {
// action to run after reload
//~ console.log(data);
$("#ifname").text(data['interface']);
$("#started").text(data['started']);
$("#last").text(data['last']);
flush_table();
check_scanner_status(ifname);
});
});
}); // END
</script>
<section class="col-xs-12">
<div id="update_stop" class="alert alert-info" role="alert" style="min-height: 65px;">
<div class="pull-left updatestatus" style="margin-top: 8px;">{{ lang._('Scan is stopped')}}</div>
</div>
<div id="update_start" class="alert alert-warning" role="alert" style="min-height: 65px; display:none;">
<div class="pull-left updatestatus" style="margin-top: 8px;">{{ lang._('Scan is running')}}</div>
</div>
<div class="content-box">
{{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_GeneralSettings'])}}
<div class="col-md-12" style="padding-bottom: 13px; padding-top: 13px;">
<button class='btn btn-default' id="stopScanner" style="margin-right: 8px;">{{ lang._('Stop') }} <i id=""></i></button>
<button class='btn btn-default' id="statusScanner" style="margin-right: 8px;">{{ lang._('Refresh') }} <i id=""></i></button>
<button class="btn btn-primary pull-center" id="startScanner" type="button"><i id="scan_progress" class=""></i><b>{{ lang._('Start') }}</b></button>
</div>
</div>
</section>
<section class="col-xs-12">
<div id="responseMsg" class="content-box" style="padding: 27px;">
<div class="table-responsive">
<table>
<thead>
<tr>
<th><b>{{ lang._('Interface name') }}</b></th>
<th><b>{{ lang._('Started') }}</b></th>
<th><b>{{ lang._('Last update') }}</b></th>
</tr>
</thead>
<tbody>
<td><p id="ifname"></p></td>
<td><p id="started"></p></td>
<td><p id="last"></p></td>
</tbody>
</table>
<hr>
<table id="netTable">
<thead>
<tr>
<td><b>{{ lang._('IP') }}</b></td>
<td><b>{{ lang._('MAC') }}</b></td>
<td><b>{{ lang._('Vendor') }}</b></td>
</tr>
</thead>
<tbody>
<tr><td></td><td></td><td></td></tr>
</tbody>
</table>
</div>
</div>
<section>
@@ -0,0 +1,153 @@
#!/usr/bin/env python2.7
"""
Copyright (c) 2017 Giuseppe De Marco <giuseppe.demarco@unical.it>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
from subprocess import Popen, PIPE
import datetime
import os
import time
import json
import sys
import re
# imports from custom cls
from ProcessIO import ProcessIO
from FileIO import FileIO
class ArpScanner(ProcessIO):
os_command_filter = """ps ax | \
grep "ARPscanner\|arp-scan -I" | \
grep {} | \
grep -v "ps ax "| \
grep -E '^[ 0-9]+'| \
awk -F' ' '{{print $1}}'"""
def __init__(self, ifname, network):
"""
netif='eth0'
network_list=['192.168.0.0/24', ...]
"""
self.ifname = ifname
self.network = network
self.result = {} # filtered output containing only what needed
# regexp used to retrieve data from arp-scan system command stdout
self.regexp = '([0-9\.]+)[\t]*([\dA-F]{2}(?:[-:][\dA-F]{2}){5})[\t]*([A-Za-z0-9\ \.\-\,\'\(\)]*)'
# os_command_filter needs '{}'.format(ifname)
self._DEBUG = False
# FileIO contains all the IO files
tmp_fileio_path = '/tmp/ARPscanner'
self.tmp = tmp_fileio_path
self.result['peers'] = []
self.result['network'] = network
self.result['interface'] = self.ifname
self.result['started'] = datetime.datetime.now().isoformat()
self.result['last_modify'] = datetime.datetime.now().isoformat()
def status(self):
"""
if arp-scan is running: parse .current file
else: parse .last file
returns json parsing of arp-scan output
"""
fout = os.path.sep.join((self.tmp, self.ifname))+'.out'
if not os.path.exists(fout):
return
self.result['last'] = time.ctime(os.path.getmtime(fout))
self.result['started'] = time.ctime(os.path.getctime(fout))
with open(fout, 'r') as f:
fcont = f.read()
#~ print(fcont)
regexp = re.findall(self.regexp , fcont, re.I)
if self._DEBUG: print(regexp)
for netfound in regexp:
self.result['peers'].append(
(netfound[0].replace('\t', ''),
netfound[1], netfound[2]))
#~ return self.result
def start(self):
"""
returns 1 if started
returns 0 if already running
"""
running = self.check_run(self.ifname, self.os_command_filter)
if running: return self.status()
fileio = FileIO(self.ifname, self.tmp)
os_command = ["arp-scan", "-I", self.ifname, self.network,
"--retry", "5"]
# run a child and detach
osc = Popen(os_command,
stdout=fileio.out,
stderr=fileio.err,
bufsize=0,
shell=False)
def get_json(self):
return json.dumps(self.result)
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-i', nargs='?', required=True,
help="network interface")
parser.add_argument('-net', nargs='?', help="""network to scan""")
parser.add_argument('-check', action="store_true", required=False,
help="check if arp-san is running on that interface")
parser.add_argument('-start', action="store_true", required=False,
help="starts arp-scan")
parser.add_argument('-stop', action="store_true", required=False,
help="Stops scanning on that interfaces")
parser.add_argument('-status', action="store_true", required=False,
help="Parse arp-scan stdout and return json")
args = parser.parse_args()
if not args.net:
args.net = '--localnet'
if args.check:
pids = ArpScanner.check_run(args.i, ArpScanner.os_command_filter)
print(pids)
sys.exit()
if args.stop:
killed = ArpScanner.stop(args.i, ArpScanner.os_command_filter)
print(killed)
sys.exit()
ap = ArpScanner(args.i, args.net)
if args.start:
ap.start()
ap.status()
print(ap.get_json())
@@ -0,0 +1,56 @@
#!/usr/bin/env python2.7
"""
Copyright (c) 2017 Giuseppe De Marco <giuseppe.demarco@unical.it>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
import os
class FileIO(object):
"""
This class manages files where data is read
.out and .err file, where os_command stdout and stderr is stored
"""
def __init__(self, name, path):
# file names
self.nerr = '{}.err'.format(name)
self.nout = '{}.out'.format(name)
# file paths
self.epath = os.path.sep.join((path, self.nerr))
self.opath = os.path.sep.join((path, self.nout))
if not os.path.exists(path):
os.makedirs(path)
# file obj, 1 means the buffer size, small as possibile to flush
# data soon as possible :)
# this feature would be better with python3
self.err = open(self.epath, 'w', buffering=0)
self.out = open(self.opath, 'w', buffering=0)
def close(self):
self.err.close()
self.out.close()
@@ -0,0 +1,179 @@
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#~ Copyright © 2017 Giuseppe De Marco <giuseppe.demarco@unical.it>
#~ All rights reserved.
#~
#~ Redistribution and use in source and binary forms, with or without modification,
#~ are permitted provided that the following conditions are met:
#~
#~ 1. Redistributions of source code must retain the above copyright notice,
#~ this list of conditions and the following disclaimer.
#~
#~ 2. Redistributions in binary form must reproduce the above copyright notice,
#~ this list of conditions and the following disclaimer in the documentation
#~ and/or other materials provided with the distribution.
#~
#~ THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
#~ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
#~ AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
#~ AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
#~ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
#~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#~ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
#~ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#~ POSSIBILITY OF SUCH DAMAGE.
import socket
import fcntl
import struct
import array
import subprocess
# get_all_interfaces
from collections import namedtuple
import re
import subprocess
import json
# From linux/sockios.h
#~ SIOCGIFCONF = 0x8912
#~ SIOCGIFINDEX = 0x8933
SIOCGIFFLAGS = 0x8913
#~ SIOCSIFFLAGS = 0x8914
SIOCGIFHWADDR = 0x8927
SIOCSIFHWADDR = 0x8924
SIOCGIFADDR = 0x8915
#~ SIOCSIFADDR = 0x8916
#~ SIOCGIFNETMASK = 0x891B
#~ SIOCSIFNETMASK = 0x891C
#~ SIOCETHTOOL = 0x8946
# From linux/if.h
IFF_UP = 0x1
# From linux/socket.h
AF_UNIX = 1
AF_INET = 2
class IPtools(object):
@staticmethod
def get_ip_address(ifname):
# python2 only
ifname = str.encode(ifname)
#
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
SIOCGIFADDR,
struct.pack('256s', ifname[:15])
)[20:24])
@staticmethod
def get_netmask(ifname):
# python2 only
ifname = str.encode(ifname)
#
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
35099,
struct.pack('256s', ifname))[20:24])
@staticmethod
def is_up(ifname):
''' Return True if the interface is up, False otherwise. '''
# python2 only
ifname = str.encode(ifname)
#
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Get existing device flags
ifreq = struct.pack('16sh', ifname, 0)
flags = struct.unpack('16sh',
fcntl.ioctl(s.fileno(),
SIOCGIFFLAGS,
ifreq))[1]
# Set new flags
if flags & IFF_UP:
return True
else:
return False
@staticmethod
def get_mac(ifname):
''' Obtain the device's mac address. '''
# python2 only
ifname = str.encode(ifname)
#
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ifreq = struct.pack('16sH14s', ifname, AF_UNIX, b'\x00'*14)
res = fcntl.ioctl(s.fileno(), SIOCGIFHWADDR, ifreq)
address = struct.unpack('16sH14s', res)[2]
mac = struct.unpack('6B8x', address)
return ":".join(['%02X' % i for i in mac])
@staticmethod
def set_mac(ifname, newmac):
''' Set the device's mac address. Device must be down for this to
succeed. '''
# python2 only
ifname = str.encode(ifname)
#
macbytes = [int(i, 16) for i in newmac.split(':')]
ifreq = struct.pack('16sH6B8x', ifname, AF_UNIX, *macbytes)
fcntl.ioctl(s.fileno(), SIOCSIFHWADDR, ifreq)
@staticmethod
def get_interfaces(json_output=False):
"""
Get a list of network interfaces on Linux.
"""
name_pattern = "^(\w+)\s"
mac_pattern = ".*?HWaddr[ ]([0-9A-Fa-f:]{17})"
ip_pattern = ".*?\n\s+inet[ ]addr:((?:\d+\.){3}\d+)"
pattern = re.compile("".join((name_pattern,
mac_pattern,
ip_pattern,
)),
flags=re.MULTILINE)
ifconfig = subprocess.check_output("ifconfig").decode()
interfaces = pattern.findall(ifconfig)
Interface = namedtuple("Interface", "name {mac} {ip}".format(
mac="mac",
ip="ip"))
res = [Interface(*interface) for interface in interfaces]
if json_output: return json.dumps(res)
return res
@staticmethod
def get_interfaces_bsd(json_output=False):
"""
Get a list of network interfaces on BSD.
"""
name_pattern = "^(\w+).+[\n\t\s]*.*[\n\t\s]*"
mac_pattern = ".*?ether[ ]([0-9A-Fa-f:]{17})[\n\t\s]*"
ip_pattern = ".*?\n\s+inet[ ]((?:\d+\.){3}\d+)"
pattern = re.compile("".join((name_pattern,
mac_pattern,
ip_pattern,
)),
flags=re.MULTILINE)
ifconfig = subprocess.check_output(["ifconfig", "-u"]).decode()
interfaces = pattern.findall(ifconfig)
Interface = namedtuple("Interface", "name {mac} {ip}".format(
mac="mac",
ip="ip"))
res = [Interface(*interface) for interface in interfaces]
if json_output: return json.dumps(res)
return res
if __name__ == '__main__':
print(IPtools.get_interfaces_bsd(json_output=True))
@@ -0,0 +1,72 @@
#!/usr/bin/env python2.7
"""
Copyright (c) 2017 Giuseppe De Marco <giuseppe.demarco@unical.it>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
from subprocess import Popen, PIPE
from os import kill, getpid, linesep
_DEBUG = False
class ProcessIO(object):
@staticmethod
def check_run(ifname, os_command_filter):
"""
returns PID if running on that ifname
else return 0
"""
mypid = getpid()
os_command = os_command_filter.format(ifname)
osc = Popen(os_command,
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
shell=True)
output, err = osc.communicate()
if _DEBUG: print(output)
if output:
pids = []
for pid in output.split(linesep):
if pid and int(pid) != mypid:
pids.append(int(pid))
return pids[:]
return 0
@classmethod
def stop(cls, ifname, os_command_filter):
""" stop scanning on that interface """
mypid = getpid()
chkrun = cls.check_run(ifname, os_command_filter)
if not chkrun: return []
pids = [int(i) for i in chkrun]
killed = []
for pid in pids:
if pid == mypid: continue
try:
kill(pid, 9)
killed.append(pid)
except Exception as e:
pass
return killed
@@ -0,0 +1,30 @@
[start]
command:python2.7 -u /usr/local/opnsense/scripts/OPNsense/ARPscanner/ARPscanner.py
parameters:-i %s -net %s -start
type:script_output
message:start arp-scan
[stop]
#~ command:ps ax | grep "ARPscanner\|arp-scan" | grep -E '[ 0-9]+' | awk -F' ' '{print $1}' | xargs | xargs kill -TERM
command:python2.7 /usr/local/opnsense/scripts/OPNsense/ARPscanner/ARPscanner.py
parameters:-i %s -stop
type:script
message:stop arp-scan
[status]
command:python2.7 -u /usr/local/opnsense/scripts/OPNsense/ARPscanner/ARPscanner.py
parameters:-i %s
type:script_output
message:status
[interfaces]
command:python2.7 /usr/local/opnsense/scripts/OPNsense/ARPscanner/IPtools.py
parameters:
type:script_output
message:get network interfaces
[check]
command:python2.7 /usr/local/opnsense/scripts/OPNsense/ARPscanner/ARPscanner.py
parameters:-i %s -check
type:script_output
message:check if arp scan is running on %s interface
@@ -0,0 +1 @@
ARPscanner.conf:/usr/local/etc/ARPscanner/ARPscanner.conf
@@ -0,0 +1,3 @@
[general]
Interface={{ OPNsense.arpscanner.general.Interface|default("") }}
Networks={{ OPNsense.arpscanner.general.Networks|default('192.168.1.0/24,172.16.45.0/25') }}