Skip to content

Commit

Permalink
Merge pull request #3 from syn-4ck/develop
Browse files Browse the repository at this point in the history
v0.1.0 ALPHA
  • Loading branch information
syn-4ck committed Apr 11, 2021
2 parents 74ce41c + 23da3cf commit d07f622
Show file tree
Hide file tree
Showing 32 changed files with 2,169 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @syn-4ck
67 changes: 67 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"

on:
push:
branches: [ main, develop ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main, develop ]
schedule:
- cron: '32 13 * * 0'

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed

steps:
- name: Checkout repository
uses: actions/checkout@v2

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main

# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl

# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language

#- run: |
# make bootstrap
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.0 ALPHA] - 2021-04-11

### Added
- CLI basic scan of Cisco IOS missconfigurations
- First scan modules: SSH & HTTP administration vulnerabilities
- Integration with Cisco API to get IOS vulnerabilities of device version
- JSON & HTML report

### Changed

### Deprecated

### Removed

### Fixed

### Security

24 changes: 24 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from setuptools import find_packages
from setuptools import setup

with open("README.md", "r") as readme_file:
readme = readme_file.read()

requirements = []

setup(
name="pynipper-ng",
version="0.1.0",
author="Syn-4ck",
author_email="repoJFM@protonmail.com",
description="",
long_description=readme,
long_description_content_type="text/markdown",
url="https://github.com/syn-4ck/pynipper-ng",
packages=find_packages(),
install_requires=requirements,
classifiers=[
"Programming Language :: Python :: 3.8",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
],
)
Empty file added src/__init__.py
Empty file.
Empty file added src/analyze/__init__.py
Empty file.
83 changes: 83 additions & 0 deletions src/analyze/analyze_device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from enum import Enum
import json
import array
import time
import configparser

from .cisco.cisco_ios_vulns import get_cisco_ios_vulns_data
from .cisco.cisco_vuln import CiscoVuln
from .cisco.parse_config import get_cisco_ios_version
from .cisco.parse_config import get_cisco_ios_hostname
from .cisco.parse_config import get_cisco_ios_passwd_enc

from .cisco.process_cisco_ios_conf import process_cisco_ios_conf

from report.report import generate_html_report, generate_json_report
from report.common.types import ReportType

from devices.common.types import DeviceType
from error.cisco_errors import GenericCiscoError

def analyze_device(args: dict) -> None:

#Cisco IOS devices
devices = DeviceType._member_names_[:3]
if args["device_type"] in devices:

print("[1/4] Initializing pynipper-ng")
hostname_cisco_device = get_cisco_ios_hostname(args["input_file"])
version_cisco_device = get_cisco_ios_version(args["input_file"])

#Get vulns by Cisco API
config_file = configparser.ConfigParser()
config_file.read(args["conf_file"])
client_id = ""
if config_file.has_option('Cisco', 'CLIENT_ID'):
client_id = config_file['Cisco']['CLIENT_ID']
client_secret = ""
if config_file.has_option('Cisco', 'CLIENT_SECRET'):
client_secret = config_file['Cisco']['CLIENT_SECRET']

vulns = get_cisco_ios_vulns_data(version_cisco_device, client_id, client_secret, args["offline"])

vulns_array = _vulns_get_fields(vulns)
vulns_array_sorted = sorted(vulns_array,reverse=True)

# Get Cisco report missconfigurations
print("[3/4] Checking missconfiguration vulnerabilities")
issues = process_cisco_ios_conf(args["input_file"])

#Generate report
print("[4/4] Generating report")
if args["output_type"] == ReportType._member_names_[0]:
generate_html_report(args["output_file"], issues, vulns_array_sorted)
elif args["output_type"] == ReportType._member_names_[1]:
generate_json_report(args["output_file"], issues, vulns_array_sorted)

def _vulns_get_fields(vulns: str) -> array:
vulns_array = []
try:
if vulns != '{}':
for vuln in vulns["advisories"]:
v = CiscoVuln(
vuln["advisoryTitle"],
vuln["summary"],
vuln["cves"],
vuln["cvssBaseScore"],
vuln["publicationUrl"]
)
vulns_array.append(v)
except KeyError:
try:
if vulns["errorCode"]:
raise GenericCiscoError(vulns["errorMessage"])
except KeyError:
raise GenericCiscoError("Unexpected error getting vulns from Cisco API")
except Exception:
raise GenericCiscoError("Unexpected error getting vulns from Cisco API")

return vulns_array

def _vuln_pretty_print(vuln: CiscoVuln) -> None:
print(vuln)

Empty file added src/analyze/cisco/__init__.py
Empty file.
47 changes: 47 additions & 0 deletions src/analyze/cisco/cisco_ios_vulns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import requests
import json
import os
from tqdm import tqdm

ACCESS_URI = "https://cloudsso.cisco.com/as/token.oauth2?client_id="
API_URI = "https://api.cisco.com/security/advisories/ios?version="

def _get_access_token(client_id: str, client_secret: str) -> str:
headers = {}
headers["Content-Type"] = "application/x-www-form-urlencoded"
url = ACCESS_URI + client_id + "&client_secret=" + client_secret + "&grant_type=client_credentials"
response = requests.post(url, headers=headers)
data = response.json()
return data['access_token']


def get_cisco_ios_vulns_data(version: str, client_id: str, client_secret: str, disable_api: bool) -> json:
if (disable_api):
print("[2/4] Cisco API disable.")
return json.dumps({})
if (client_id=="" or client_secret==""):
print("[2/4] No Cisco API Credentials. Skipping get API data...")
return json.dumps({})
access_token = _get_access_token(client_id, client_secret)
headers = {}
headers["Authorization"] = 'Bearer ' + access_token
url = API_URI + version
response = requests.get(url, headers=headers, stream=True)

#Implement a progress bar
print("[2/4] Connecting and getting data from Cisco API")
total_size_in_bytes= int(response.headers.get('content-length', 0))
block_size = 1024
progress_bar = tqdm(total=total_size_in_bytes, unit_divisor=1024, unit='B', unit_scale=True, leave = True)
f = open('api-data.dat', 'wb')
for data in response.iter_content(block_size):
progress_bar.update(len(data))
f.write(data)
progress_bar.close()
f.close()

readable_file = open('api-data.dat', 'r')
file_data = readable_file.read()
readable_file.close()
os.remove('api-data.dat')
return json.loads(file_data)
26 changes: 26 additions & 0 deletions src/analyze/cisco/cisco_vuln.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class CiscoVuln:

def __init__(self, title, summary, cves, cvss, url):
self.title = title
self.summary = summary
self.cves = cves
if (cvss=='NA'):
cvss=0.0
self.cvss = float(cvss)
self.url = url

def __str__(self):
print("Vulnerability " + self.title + ":")
print("===================================================================================================")
print("Summary: " + self.summary)
print("CVEs: " + str(self.cves))
print("CVSS: " + self.cvss)
print("Publication URL: " + self.url)
print("\n---------------------------------------------------------------------------------------------------")
return("")

def __eq__(self, other):
return self.title == other.title

def __lt__(self, other):
return self.cvss < other.cvss
Empty file.
18 changes: 18 additions & 0 deletions src/analyze/cisco/ios_process/cisco_ios_issue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class CiscoIOSIssue:

def __init__(self, title, observation, impact, ease, recommendation):
self.title = title
self.observation = observation
self.impact = impact
self.ease = ease
self.recommendation = recommendation

def __str__(self):
print("Issue " + self.title + ":")
print("===================================================================================================")
print("Observation: " + self.observation)
print("Impact: " + self.impact)
print("Ease: " + self.ease)
print("Recommendation: " + self.recommendation)
print("\n---------------------------------------------------------------------------------------------------")
return("")
52 changes: 52 additions & 0 deletions src/analyze/cisco/ios_process/modules/http_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from analyze.cisco.parse_config import parse_cisco_ios_config_file
from ...ios_process.cisco_ios_issue import CiscoIOSIssue

# If the device has http configured -> true
def _has_http(filename: str) -> bool:
parser = parse_cisco_ios_config_file(filename)
http_enable = parser.find_objects("ip http server")
http_disable = parser.find_objects("no ip http server")
if (len(http_enable) > 0):
return True
elif (len(http_disable) > 0):
return False
else:
return True #by default IOS Cisco devices has HTTP

def get_cisco_ios_http(issues: list,filename: str) -> CiscoIOSIssue:
if (_has_http(filename)):
http_issue = CiscoIOSIssue(
"HyperText Transport Protocol Service",
"Recent Cisco IOS-based devices support web-based administration using the HTTP protocol. Cisco web-based administration facilities can sometimes be basic but they do provide a simple method of administering remote devices. However, HTTP is a clear-text protocol and is vulnerable to various packet-capture techniques.",
"An attacker who was able to monitor network traffic could capture authentication credentials.",
"Network packet and password sniffing tools are widely available on the Internet. Once authentication credentials have been captured it is trivial to use the credentials to log in using the captured credentials.",
"It is recommended that, if not required, the HTTP service be disabled. If a remote method of access to the device is required, consider using HTTPS or SSH. The encrypted HTTPS and SSH services may require a firmware or hardware upgrade. The HTTP service can be disabled with the following IOS command: no ip http server. If it is not possible to upgrade the device to use the encrypted HTTPS or SSH services, additional security can be configured."
)
issues.append(http_issue)

# Number of access list should be used to restrict access to HTTP server
def get_cisco_ios_http_access_list(filename: str) -> str:
parser = parse_cisco_ios_config_file(filename)
access_list = parser.find_objects("ip http access-class")
if (len(access_list) > 0):
num = host[0].re_match_typed(r'^ip http access-class\s+(\S+)', default='')
return num
else:
return "Undefined"

# Kind of auth in HTTP server
def get_cisco_ios_http_auth(filename: str) -> str:
parser = parse_cisco_ios_config_file(filename)
timeout = parser.find_objects("ip http auth")
if (len(timeout) > 0):
type = host[0].re_match_typed(r'^ip http auth(entication)?\s+(\S+)', default='')
return seconds.split( )[0]
else:
return None

def get_http_missconfigurations(filename: str) -> list:
issues = []
get_cisco_ios_http(issues,filename)
#get_cisco_ios_http_access_list(issues,filename)
#get_cisco_ios_http_auth(issues,filename)
return issues
Loading

0 comments on commit d07f622

Please sign in to comment.