Skip to content
This repository has been archived by the owner on Nov 29, 2021. It is now read-only.

Add <targets> element to <start_scan>. #34

Merged
merged 4 commits into from
Jul 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 51 additions & 10 deletions doc/OSP.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
<start_scan>
<attributes>
<scan_id>Optional UUID value to set as scan ID</scan_id>
<target>Target host to scan</target>
<ports>Ports list to scan</ports>
<target>Target hosts to scan in a comma-separated list</target>
<ports>Ports list to scan as comma-separated list</ports>
</attributes>
<elements>
<scanner_params>
Expand Down Expand Up @@ -575,10 +575,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
<vts>
<vt id="1.2.3.4.5">
<name>Check for presence of vulnerabilty X</name>
<custom>
<custom>
<my_element>First custom element</my_element>
<my_other_element>second custom element</my_other_element>
</custom>
</custom>
</vt>
</vts>
</get_vts_response>
Expand All @@ -594,7 +594,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
<vts>
<vt id="1.2.3.4.5">
<name>Check for presence of vulnerabilty X</name>
<vt_params>
<vt_params>
<vt_param id="timeout" type="integer">
<name>Timeout</name>
<description>Vulnerability Test Timeout</description>
Expand All @@ -605,11 +605,11 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
<description />
<default>1</default>
</vt_param>
</vt_params>
<custom>
</vt_params>
<custom>
<my_element>First custom element</my_element>
<my_other_element>second custom element</my_other_element>
</custom>
</custom>
</vt>
</vts>
</get_vts_response>
Expand All @@ -623,12 +623,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
<pattern>
<attrib>
<name>target</name>
<summary>Target host to scan</summary>
<summary>Target hosts to scan in a comma-separated list</summary>
<type>string</type>
</attrib>
<attrib>
<name>ports</name>
<summary>Ports list to scan</summary>
<summary>Ports list to scan as comma-separated list</summary>
<type>string</type>
</attrib>
<attrib>
Expand All @@ -647,6 +647,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
<name>vts</name>
<summary>Contanins elements that represent Vulnerability Test to be excecute and their parameters</summary>
</ele>
<ele>
<name>targets</name>
<summary>Contanins elements that represent a target to execute a scan against. If target and port attributes are present this elemen is not take in account</summary>
</ele>
<response>
<pattern>
<attrib>
Expand Down Expand Up @@ -704,6 +708,35 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
</start_scan_response>
</response>
</example>
<example>
<summary>Start a new scan with multi-targets</summary>
<request>
<start_scan>
<scanner_params />
<vts>
<vt id='1.3.6.1.4.1.25623.1.0.10662'>
<vt_param name='XYZ JKL' type='entry'>200</vt_param>
<vt_param name='ABC' type='checkbox'>yes</vt_param>
</vt>
</vts>
<targets>
<target>
<hosts>localhost</hosts>
<ports>80,443</ports>
</target>
<target>
<hosts>192.168.1.0/24</hosts>
<ports>1,2,3,80,443</ports>
</target>
</targets>
</start_scan>
</request>
<response>
<start_scan_response status_text="OK" status="200">
<id>2f616d53-595f-4785-9b97-4395116ca118</id>
</start_scan_response>
</response>
</example>
</command>

<command>
Expand Down Expand Up @@ -788,6 +821,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
</description>
<version>1.2</version>
</change>
<change>
<command>START_SCAN</command>
<summary>target optional element added </summary>
<description>
Added optional element targets to specify different hosts with a different port list. This is take in account only if target and port attributes are not present in start_scan tag.
</description>
<version>1.2</version>
</change>


<change>
Expand Down
29 changes: 18 additions & 11 deletions ospd/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,15 @@ def ids_iterator(self):

return iter(self.scans_table.keys())

def create_scan(self, scan_id='', target='', ports='', options=dict(), vts=''):
def create_scan(self, scan_id='', targets='', options=dict(), vts=''):
""" Creates a new scan with provided scan information. """

if self.data_manager is None:
self.data_manager = multiprocessing.Manager()
scan_info = self.data_manager.dict()
scan_info['results'] = list()
scan_info['progress'] = 0
scan_info['target'] = target
scan_info['ports'] = ports
scan_info['targets'] = targets
scan_info['vts'] = vts
scan_info['options'] = options
scan_info['start_time'] = int(time.time())
Expand Down Expand Up @@ -162,14 +161,22 @@ def get_end_time(self, scan_id):
return self.scans_table[scan_id]['end_time']

def get_target(self, scan_id):
""" Get a scan's target. """

return self.scans_table[scan_id]['target']

def get_ports(self, scan_id):
""" Get a scan's ports list. """

return self.scans_table[scan_id]['ports']
""" Get a scan's target list. """

return self.scans_table[scan_id]['targets']

def get_ports(self, scan_id, target):
""" Get a scan's ports list. If a target is specified
it will return the corresponding port for it. If not,
it returns the port item of the first nested list in
the target's list.
"""
if target:
for item in self.scans_table[scan_id]['targets']:
if target == item[0]:
return item[1]

return self.scans_table[scan_id]['targets'][0][1]

def get_vts(self, scan_id):
""" Get a scan's vts list. """
Expand Down
110 changes: 80 additions & 30 deletions ospd/ospd.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,18 +422,69 @@ def process_vts_params(self, scanner_vts):
vts[vt_id][pname] = {'type': ptype, 'value': pvalue}
return vts

@staticmethod
def process_targets_element(scanner_target):
""" Receive an XML object with the target, ports to run
a scan against.

@param: XML element with target subelements. Each target has <hosts>
and <ports> subelements. Hosts can be a single host, a host range,
a comma-separated host list or a network address. <ports> is optional,
therefor each ospd-scanner should check for a valid port if needed.

Example form:
<targets>
<target>
<hosts>localhosts</hosts>
<ports>80,443</ports>
</target>
<target>
<hosts>192.168.0.0/24</hosts>
<ports>22</ports>
</target>
</targets>

@return: A list of (hosts, port) tuples.
Example form:
[('localhost', '80,43'), ('192.168.0.0/24', '22')]
"""

target_list = []
for target in scanner_target:
for child in target:
if child.tag == 'hosts':
hosts = child.text
ports = ''
if child.tag == 'ports':
ports = child.text
if hosts:
target_list.append([hosts, ports])
else:
raise OSPDError('No target to scan', 'start_scan')

return target_list

def handle_start_scan_command(self, scan_et):
""" Handles <start_scan> command.

@return: Response string for <start_scan> command.
"""

target_str = scan_et.attrib.get('target')
if target_str is None:
raise OSPDError('No target attribute', 'start_scan')
ports_str = scan_et.attrib.get('ports')
if ports_str is None:
raise OSPDError('No ports attribute', 'start_scan')
# For backward compatibility, if target and ports attributes are set,
# <targets> element is ignored.
if target_str is None or ports_str is None:
target_list = scan_et.find('targets')
if target_list is None or not target_list:
raise OSPDError('No targets or ports', 'start_scan')
else:
scan_targets = self.process_targets_element(target_list)
else:
scan_targets = []
for single_target in target_str_to_list(target_str):
scan_targets.append([single_target, ports_str])

scan_id = scan_et.attrib.get('scan_id')
if scan_id is not None and scan_id != '' and not valid_uuid(scan_id):
raise OSPDError('Invalid scan_id UUID', 'start_scan')
Expand Down Expand Up @@ -461,10 +512,9 @@ def handle_start_scan_command(self, scan_et):
scan_func = self.start_scan
scan_params = self.process_scan_params(params)

scan_id = self.create_scan(scan_id, target_str,
ports_str, scan_params, vts)
scan_id = self.create_scan(scan_id, scan_targets, scan_params, vts)
scan_process = multiprocessing.Process(target=scan_func,
args=(scan_id, target_str))
args=(scan_id, scan_targets))
self.scan_processes[scan_id] = scan_process
scan_process.start()
id_ = ET.Element('id')
Expand Down Expand Up @@ -625,51 +675,52 @@ def handle_client_stream(self, stream, is_unix=False):
else:
stream.write(response)

def start_scan(self, scan_id, target_str):
def start_scan(self, scan_id, targets):
""" Starts the scan with scan_id. """

os.setsid()
logger.info("{0}: Scan started.".format(scan_id))
target_list = target_str_to_list(target_str)
if target_list is None:
target_list = targets
if target_list is None or not target_list:
raise OSPDError('Erroneous targets list', 'start_scan')
for index, target in enumerate(target_list):
progress = float(index) * 100 / len(target_list)
self.set_scan_progress(scan_id, int(progress))
logger.info("{0}: Host scan started.".format(target))
logger.info("{0}: Host scan started on ports {1}.".format(target[0],target[1]))
try:
ret = self.exec_scan(scan_id, target)
ret = self.exec_scan(scan_id, target[0])
if ret == 0:
self.add_scan_host_detail(scan_id, name='host_status',
host=target, value='0')
host=target[0], value='0')
elif ret == 1:
self.add_scan_host_detail(scan_id, name='host_status',
host=target, value='1')
host=target[0], value='1')
elif ret == 2:
self.add_scan_host_detail(scan_id, name='host_status',
host=target, value='2')
host=target[0], value='2')
else:
logger.debug('{0}: No host status returned'.format(target))
logger.debug('{0}: No host status returned'.format(target[0]))
except Exception as e:
self.add_scan_error(scan_id, name='', host=target,
self.add_scan_error(scan_id, name='', host=target[0],
value='Host process failure (%s).' % e)
logger.exception('While scanning {0}:'.format(target))
logger.exception('While scanning {0}:'.format(target[0]))
else:
logger.info("{0}: Host scan finished.".format(target))
logger.info("{0}: Host scan finished.".format(target[0]))

self.finish_scan(scan_id)

def dry_run_scan(self, scan_id, target_str):
def dry_run_scan(self, scan_id, targets):
""" Dry runs a scan. """

os.setsid()
target_list = target_str_to_list(target_str)
for _, target in enumerate(target_list):
host = resolve_hostname(target)
#target_list = target_str_to_list(target_str)
for _, target in enumerate(targets):
host = resolve_hostname(target[0])
if host is None:
logger.info("Couldn't resolve {0}.".format(target))
logger.info("Couldn't resolve {0}.".format(target[0]))
continue
logger.info("{0}: Dry run mode.".format(host))
port = self.get_scan_ports(scan_id, target=target[0])
logger.info("{0}:{1}: Dry run mode.".format(host, port))
self.add_scan_log(scan_id, name='', host=host,
value='Dry run result')
self.finish_scan(scan_id)
Expand Down Expand Up @@ -1052,16 +1103,15 @@ def run(self, address, port, unix_path):
sock.shutdown(socket.SHUT_RDWR)
sock.close()

def create_scan(self, scan_id, target, ports, options, vts):
def create_scan(self, scan_id, targets, options, vts):
""" Creates a new scan.

@target: Target to scan.
@options: Miscellaneous scan options.

@return: New scan's ID.
"""
return self.scan_collection.create_scan(scan_id, target,
ports, options, vts)
return self.scan_collection.create_scan(scan_id, targets, options, vts)

def get_scan_options(self, scan_id):
""" Gives a scan's list of options. """
Expand Down Expand Up @@ -1091,9 +1141,9 @@ def get_scan_target(self, scan_id):
""" Gives a scan's target. """
return self.scan_collection.get_target(scan_id)

def get_scan_ports(self, scan_id):
def get_scan_ports(self, scan_id, target=''):
""" Gives a scan's ports list. """
return self.scan_collection.get_ports(scan_id)
return self.scan_collection.get_ports(scan_id, target)

def get_scan_vts(self, scan_id):
""" Gives a scan's vts list. """
Expand Down
2 changes: 1 addition & 1 deletion tests/testSSHDaemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def testNoParamiko(self):
def testRunCommand(self):
ospd_ssh.paramiko = fakeparamiko
daemon = OSPDaemonSimpleSSH('cert', 'key', 'ca')
scanid = daemon.create_scan(None, 'host.example.com', '80, 443',
scanid = daemon.create_scan(None, ['host.example.com', '80, 443'],
dict(port=5, ssh_timeout=15,
username_password='dummy:pw'), '')
res = daemon.run_command(scanid, 'host.example.com', 'cat /etc/passwd')
Expand Down
16 changes: 16 additions & 0 deletions tests/testScanAndResult.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,19 @@ def testBillonLaughs(self):
' <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">' +
']>')
self.assertRaises(EntitiesForbidden, daemon.handle_command, lol)

def testScanMultiTarget(self):
daemon = DummyWrapper([])
response = secET.fromstring(
daemon.handle_command('<start_scan>' +
'<scanner_params /><vts><vt id="1.2.3.4" />' +
'</vts>' +
'<targets><target>' +
'<hosts>localhosts</hosts>' +
'<ports>80,443</ports>' +
'</target>' +
'<target><hosts>192.168.0.0/24</hosts>' +
'<ports>22</ports></target></targets>' +
'</start_scan>'))
print(ET.tostring(response))
self.assertEqual(response.get('status'), '200')