├── .coveragerc ├── .github └── workflows │ ├── preflight_check.yaml │ └── pypi_publish.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── CODEOWNERS ├── LICENCE ├── MANIFEST ├── MANIFEST.in ├── NOTICE ├── README.rst ├── TODO ├── config └── database.yml ├── docs ├── Makefile ├── conf.py ├── diff.rst ├── index.rst ├── make.bat ├── objects.rst ├── objects │ ├── cpe.rst │ ├── nmaphost.rst │ ├── nmapreport.rst │ ├── nmapservice.rst │ └── os.rst ├── parser.rst ├── plugins_s3.rst └── process.rst ├── examples ├── check_cpe.py ├── diff_sample1.py ├── diff_sample2.py ├── elastikibana.py ├── es_plugin.py ├── json_serialize.py ├── kibanalibnmap.png ├── nmap_task.py ├── nmap_task_bg.py ├── os_fingerprint.py ├── proc_async.py └── proc_nmap_like.py ├── libnmap ├── __init__.py ├── diff.py ├── objects │ ├── __init__.py │ ├── cpe.py │ ├── host.py │ ├── os.py │ ├── report.py │ └── service.py ├── parser.py ├── plugins │ ├── __init__.py │ ├── backendplugin.py │ ├── backendpluginFactory.py │ ├── es.py │ ├── mongodb.py │ ├── s3.py │ └── sql.py ├── process.py ├── reportjson.py └── test │ ├── __init__.py │ ├── files │ ├── 1_host_ping.xml │ ├── 1_hosts.xml │ ├── 1_hosts_banner.xml │ ├── 1_hosts_banner_ports.xml │ ├── 1_hosts_banner_ports_notsyn.xml │ ├── 1_hosts_banner_ports_xmas.xml │ ├── 1_hosts_diff.xml │ ├── 1_hosts_down.xml │ ├── 1_hosts_nohostname.xml │ ├── 1_os_banner_scripts.xml │ ├── 2_hosts.json │ ├── 2_hosts.xml │ ├── 2_hosts_achange.xml │ ├── 2_hosts_version.xml │ ├── 2_null_hosts.xml │ ├── 2_tcp_hosts.xml │ ├── defused_et_included.xml │ ├── defused_et_local_includer.xml │ ├── diff_1_host_ping_mac_changed.xml │ ├── dionaea_scan.xml │ ├── extra_ports.xml │ ├── full_sudo5.xml │ ├── full_sudo6.xml │ ├── fullscan.xml │ ├── os_scan5.xml │ ├── os_scan6.xml │ └── test_osclass.xml │ ├── process-stressbox │ ├── check_fqp_nmap.py │ ├── multi_nmap_process.py │ ├── multi_nmap_process_background.py │ ├── proc_async.py │ ├── proc_nmap_like.py │ ├── stop_scan.py │ ├── stressback.py │ └── stresstest.py │ ├── test_backend_plugin_factory.py │ ├── test_cpe.py │ ├── test_defusedxml.py │ ├── test_extraports.py │ ├── test_fp.py │ ├── test_host.py │ ├── test_new_parser.py │ ├── test_parser.py │ ├── test_process.py │ ├── test_report.py │ ├── test_report_diff.py │ ├── test_reportjson.py │ └── test_service.py ├── requirements-dev.txt ├── setup.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | */python?.?/* 4 | */site-packages/nose/* 5 | -------------------------------------------------------------------------------- /.github/workflows/preflight_check.yaml: -------------------------------------------------------------------------------- 1 | name: Preflight Check 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-22.04 14 | strategy: 15 | matrix: 16 | python-version: ["3.8", "3.9", "3.10"] 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install black isort flake8 27 | - name: Format checker with psf/black 28 | uses: psf/black@stable 29 | with: 30 | options: "--check -l 79 --exclude docs/" 31 | version: "24.3.0" 32 | - name: Format checker with isort 33 | run: isort --check-only -m 3 -l 79 --profile=black . 34 | - name: Lint with flake8 35 | run: flake8 --exclude test,docs,examples . 36 | test: 37 | runs-on: ubuntu-22.04 38 | strategy: 39 | matrix: 40 | python-version: ["3.8", "3.9", "3.10"] 41 | steps: 42 | - uses: actions/checkout@v4 43 | - name: Setup Python ${{ matrix.python-version }} 44 | uses: actions/setup-python@v5 45 | with: 46 | python-version: ${{ matrix.python-version }} 47 | - name: Setup Environment 48 | run: | 49 | python -m pip install --upgrade pip 50 | pip install pytest pytest-cov defusedxml 51 | pip install coveralls 52 | sudo apt-get install -y nmap 53 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 54 | - name: Test with pytest 55 | run: | 56 | pytest --cov=libnmap/ --ignore=libnmap/test/test_backend_plugin_factory.py 57 | - name: Upload Coverage 58 | if: matrix.python-version != '2.7' 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | COVERALLS_FLAG_NAME: ${{ matrix.python-version }} 62 | COVERALLS_PARALLEL: true 63 | run: | 64 | coveralls --service=github 65 | coveralls: 66 | name: Finish Coveralls 67 | needs: test 68 | runs-on: ubuntu-22.04 69 | container: python:3-slim 70 | steps: 71 | - name: Finished 72 | run: | 73 | pip3 install --upgrade coveralls 74 | coveralls --finish --service=github 75 | env: 76 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 77 | -------------------------------------------------------------------------------- /.github/workflows/pypi_publish.yaml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up Python 13 | uses: actions/setup-python@v2 14 | with: 15 | python-version: '3.x' 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install setuptools wheel twine 20 | - name: Build and publish 21 | env: 22 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 23 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 24 | run: | 25 | python setup.py sdist 26 | twine upload dist/* 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | *.swp 3 | .pylintrc 4 | *~ 5 | *.lock 6 | *.DS_Store 7 | *.swp 8 | *.out 9 | *.sqlite3 10 | *~ 11 | 12 | # Packages 13 | *.egg 14 | *.egg-info 15 | dist 16 | build 17 | eggs 18 | parts 19 | bin 20 | var 21 | sdist 22 | develop-eggs 23 | .installed.cfg 24 | lib 25 | lib64 26 | 27 | # Installer logs 28 | pip-log.txt 29 | 30 | # Unit test / coverage reports 31 | .coverage 32 | .tox 33 | nosetests.xml 34 | 35 | # Translations 36 | *.mo 37 | 38 | # Mr Developer 39 | .mr.developer.cfg 40 | .project 41 | .pydevproject 42 | .swp 43 | 44 | __pycache__ 45 | .vscode/settings.json 46 | .noseids 47 | _build 48 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: ^(test/|.tox/|docs) 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v2.3.0 5 | hooks: 6 | - id: check-yaml 7 | - id: end-of-file-fixer 8 | - id: trailing-whitespace 9 | - repo: https://github.com/psf/black 10 | rev: 24.3.0 11 | hooks: 12 | - id: black 13 | args: [--line-length=79] 14 | files: ^libnmap 15 | - repo: https://github.com/pre-commit/mirrors-isort 16 | rev: v5.6.4 17 | hooks: 18 | - id: isort 19 | args: [--multi-line=3, --line-length=79, --profile=black] 20 | - repo: https://gitlab.com/pycqa/flake8 21 | rev: 3.8.4 22 | hooks: 23 | - id: flake8 24 | exclude: ^libnmap/(test/|docs/|examples/) 25 | - repo: local 26 | hooks: 27 | - id: pytest-check 28 | name: pytest-check 29 | stages: [pre-commit] 30 | types: [python] 31 | entry: pytest --cov=libnmap/ --ignore=libnmap/test/test_backend_plugin_factory.py 32 | language: system 33 | pass_filenames: false 34 | always_run: true 35 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @savon-noir 2 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Ronald Bister 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | README.rst 3 | TODO 4 | requirements-dev.txt 5 | setup.py 6 | docs/diff.rst 7 | docs/index.rst 8 | docs/objects.rst 9 | docs/parser.rst 10 | docs/plugins_s3.rst 11 | docs/process.rst 12 | docs/objects/cpe.rst 13 | docs/objects/nmaphost.rst 14 | docs/objects/nmapreport.rst 15 | docs/objects/nmapservice.rst 16 | docs/objects/os.rst 17 | libnmap/__init__.py 18 | libnmap/diff.py 19 | libnmap/parser.py 20 | libnmap/process.py 21 | libnmap/reportjson.py 22 | libnmap/objects/__init__.py 23 | libnmap/objects/cpe.py 24 | libnmap/objects/host.py 25 | libnmap/objects/os.py 26 | libnmap/objects/report.py 27 | libnmap/objects/service.py 28 | libnmap/plugins/__init__.py 29 | libnmap/plugins/backendplugin.py 30 | libnmap/plugins/backendpluginFactory.py 31 | libnmap/plugins/es.py 32 | libnmap/plugins/mongodb.py 33 | libnmap/plugins/s3.py 34 | libnmap/plugins/sql.py 35 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include TODO 2 | include *.rst *.txt 3 | recursive-include docs *.rst 4 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | python-libnmap 2 | Copyright 2020 Ronald Bister 3 | 4 | This product includes software developed by Ronald Bister 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | python-libnmap 2 | ============== 3 | 4 | Code status 5 | ----------- 6 | 7 | |preflight-check| |Coverage Status| |License| 8 | 9 | Use cases 10 | --------- 11 | 12 | libnmap is a python library enabling python developers to manipulate 13 | nmap process and data. 14 | 15 | libnmap is what you were looking for if you need to implement the 16 | following: 17 | 18 | - automate or schedule nmap scans on a regular basis 19 | - manipulate nmap scans results to do reporting 20 | - compare and diff nmap scans to generate graphs 21 | - batch process scan reports 22 | - … 23 | 24 | The above uses cases will be easy to implement with the help of the 25 | libnmap modules. 26 | 27 | libnmap modules 28 | --------------- 29 | 30 | The lib currently offers the following modules: 31 | 32 | - **process**: enables you to launch nmap scans 33 | - **parse**: enables you to parse nmap reports or scan results (only 34 | XML so far) from a file, a string,… 35 | - **report**: enables you to manipulate a parsed scan result and 36 | de/serialize scan results in a json format 37 | - **diff**: enables you to see what changed between two scans 38 | - **common**: contains basic nmap objects like NmapHost and 39 | NmapService. It is to note that each object can be "diff()ed" with 40 | another similar object. 41 | - **plugins**: enables you to support datastores for your scan results 42 | directly in the "NmapReport" object. from report module: 43 | 44 | - mongodb: insert/get/getAll/delete 45 | - sqlalchemy: insert/get/getAll/delete 46 | - aws s3: insert/get/getAll/delete (not supported for python3 since 47 | boto is not supporting py3) 48 | - csv: todo (easy to implement) 49 | - elastic search: todo 50 | 51 | Documentation 52 | ------------- 53 | 54 | All the documentation is available on `read the 55 | docs `__. This documentation contains 56 | small code samples that you directly reuse. 57 | 58 | Dependencies 59 | ------------ 60 | 61 | libnmap has by default no dependencies, except defusedxml if you need to 62 | import untrusted XML scans data. 63 | 64 | The only additional python modules you’ll have to install depends if you 65 | wish to use libnmap to store reports on an exotic data store via 66 | libnmap’s independents plugins. 67 | 68 | Below the list of optional dependencies: 69 | 70 | - `sqlalchemy `__ (+the driver 71 | ie:MySQL-python) 72 | - `pymongo `__ 73 | - `boto `__ 74 | 75 | Security 76 | -------- 77 | 78 | If you are importing/parsing untrusted XML scan outputs with 79 | python-libnmap, install defusedxml library: 80 | 81 | .. code:: bash 82 | 83 | ronald@brouette:~/dev$ pip install defusedxml 84 | 85 | This will prevent you from being vulnerable to `XML External Entities 86 | attacks `__. 87 | 88 | For more information, read the `official libnmap 89 | documentation `__ 90 | 91 | This note relates to a cascaded CVE vulnerability from the python core 92 | library XML ElementTree. Nevertheless, python-libnmap has been assigned 93 | an `official 94 | CVE `__ 95 | to track this issue. 96 | 97 | This CVE is addressed from v0.7.2. 98 | 99 | Python Support 100 | -------------- 101 | 102 | The libnmap code is tested against the following python interpreters: 103 | 104 | - Python 2.7 105 | - Python 3.6 106 | - Python 3.7 107 | - Python 3.8 108 | 109 | Install 110 | ------- 111 | 112 | You can install libnmap via pip: 113 | 114 | .. code:: bash 115 | 116 | ronald@brouette:~$ pip install python-libnmap 117 | 118 | or via git and pip: 119 | 120 | .. code:: bash 121 | 122 | ronald@brouette:~$ git clone https://github.com/savon-noir/python-libnmap.git 123 | ronald@brouette:~$ cd python-libnmap 124 | ronald@brouette:~$ pip install . 125 | 126 | or via git and dist utils (à l’ancienne/deprecated): 127 | 128 | .. code:: bash 129 | 130 | ronald@brouette:~$ git clone https://github.com/savon-noir/python-libnmap.git 131 | ronald@brouette:~$ cd python-libnmap 132 | ronald@brouette:~$ python setup.py install 133 | 134 | 135 | Examples 136 | -------- 137 | 138 | Some codes samples are available in the examples directory or in the 139 | `documentation `__. 140 | 141 | Among other example, you notice an sample code pushing nmap scan reports 142 | in an ElasticSearch instance and allowing you to create fancy dashboards 143 | in Kibana like the screenshot below: 144 | 145 | .. figure:: https://github.com/savon-noir/python-libnmap/blob/es/examples/kibanalibnmap.png 146 | :alt: Kibanane 147 | 148 | Contributors 149 | ------------ 150 | 151 | Mike @bmx0r Boutillier for S3 and SQL-Alechemy plugins and for the 152 | constructive critics. Thanks! 153 | 154 | .. |preflight-check| image:: https://github.com/savon-noir/python-libnmap/workflows/Preflight%20Check/badge.svg 155 | .. |Coverage Status| image:: https://coveralls.io/repos/github/savon-noir/python-libnmap/badge.svg?branch=master 156 | :target: https://coveralls.io/github/savon-noir/python-libnmap?branch=master 157 | .. |License| image:: https://img.shields.io/badge/License-Apache%202.0-blue.svg 158 | :target: https://opensource.org/licenses/Apache-2.0 159 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 0.7.2: clean-up blacked code and pylint it 2 | 0.7.2: add unittest for defusedxml to fix billionlaugh and external entities security issues 3 | 0.7.2: Change License from CC-BY to Apache 2.0 4 | 0.7.2: Enabled defusedxml support as preferred option for parsing () 5 | 0.7.2: add extra_requires for plugins deps and defusedxml 6 | 0.7.2: Remove code duplication in sudo_run and sudo_run_background from process.py 7 | 0.7.2: Fix empty nmap outputs due to subprocess race condition (Merge PR79 from @Shouren) 8 | 0.7.2: Added banner_dict support + unittest (Merge edited PR from @cfoulds) 9 | 10 | release: 11 | - changelog date not respecting KACL specs 12 | - check https://github.com/anton-yurchenko/git-release 13 | - https://github.com/sean0x42/markdown-extract 14 | 15 | Contribution file: 16 | - specify where version needs to be set before adding tag to commit 17 | - libnmap/__init__.py 18 | - docs/conf.py 19 | - setup.py 20 | - CHANGELOG.md (set correct date) 21 | 22 | 0.7.3: add CSV backend support 23 | 0.7.3: improve API for NSE scripts 24 | 0.7.3: add support for post,pre and host scripts 25 | 0.7.3: add a Contribution guideline page 26 | 0.7.3: add development environment config and setup 27 | 0.7.3: add pre-commit hooks to enforce black and isort 28 | 0.7.3: automate in github actions the git workflow + doc update + pypi update 29 | 30 | 0.7.4: Add support and tests for traceroute in nmap 31 | 32 | 0.7.5: create complete python testing environment based on docker-compose and some examples 33 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | sqlite: 2 | adapter: sqlite3 3 | database: "/tmp/reportdb.sql" 4 | timeout: 500 5 | mysql: 6 | adapter: mysql2 7 | database: poulet 8 | username: 9 | encoding: utf8 10 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/libnmap.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/libnmap.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/libnmap" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/libnmap" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/diff.rst: -------------------------------------------------------------------------------- 1 | libnmap.diff 2 | ============== 3 | 4 | Using libnmap.diff module 5 | ------------------------- 6 | 7 | This modules enables the user to diff two NmapObjects: NmapService, NmapHost, NmapReport. 8 | 9 | The constructor returns a NmapDiff object which he can then use to call its inherited methods: 10 | 11 | - added() 12 | - removed() 13 | - changed() 14 | - unchanged() 15 | 16 | Those methods return a python set() of keys which have been changed/added/removed/unchanged from one 17 | object to another. 18 | The keys of each objects could be found in the implementation of the get_dict() methods of the compared objects. 19 | 20 | The example below is a heavy version of going through all nested objects to see what has changed after a diff:: 21 | 22 | #!/usr/bin/env python 23 | 24 | from libnmap.parser import NmapParser 25 | 26 | rep1 = NmapParser.parse_fromfile('libnmap/test/files/1_hosts.xml') 27 | rep2 = NmapParser.parse_fromfile('libnmap/test/files/1_hosts_diff.xml') 28 | 29 | rep1_items_changed = rep1.diff(rep2).changed() 30 | changed_host_id = rep1_items_changed.pop().split('::')[1] 31 | 32 | changed_host1 = rep1.get_host_byid(changed_host_id) 33 | changed_host2 = rep2.get_host_byid(changed_host_id) 34 | host1_items_changed = changed_host1.diff(changed_host2).changed() 35 | 36 | changed_service_id = host1_items_changed.pop().split('::')[1] 37 | changed_service1 = changed_host1.get_service_byid(changed_service_id) 38 | changed_service2 = changed_host2.get_service_byid(changed_service_id) 39 | service1_items_changed = changed_service1.diff(changed_service2).changed() 40 | 41 | for diff_attr in service1_items_changed: 42 | print "diff({0}, {1}) [{2}:{3}] [{4}:{5}]".format(changed_service1.id, 43 | changed_service2.id, 44 | diff_attr, 45 | getattr(changed_service1, diff_attr), 46 | diff_attr, 47 | getattr(changed_service2, diff_attr)) 48 | 49 | This outputs the following line:: 50 | 51 | (pydev)$ python /tmp/z.py 52 | diff(tcp.3306, tcp.3306) [state:open] [state:filtered] 53 | (pydev)$ 54 | 55 | Of course, the above code is quite ugly and heavy but the idea behind diff was to be as generic as possible in order to 56 | let the user of the lib defines its own algorithms to extract the data. 57 | 58 | A less manual and more clever approach would be to recursively retrieve the changed attributes and values of nested objects. 59 | Below, you will find a small code example doing it 60 | 61 | .. literalinclude:: ../examples/diff_sample2.py 62 | 63 | This code will output the following:: 64 | 65 | ~ NmapReport: started at 1361737906 hosts up 2/2 hosts_total: 1 => 2 66 | ~ NmapReport: started at 1361737906 hosts up 2/2 commandline: nmap -sT -vv -oX 1_hosts.xml localhost => nmap -sS -vv -oX 2_hosts.xml localhost scanme.nmap.org 67 | ~ NmapReport: started at 1361737906 hosts up 2/2 hosts_up: 1 => 2 68 | ~ NmapService: [closed 25/tcp smtp ()] state: open => closed 69 | + NmapService: [open 23/tcp telnet ()] 70 | - NmapService: [open 111/tcp rpcbind ()] 71 | ~ NmapReport: started at 1361737906 hosts up 2/2 scan_type: connect => syn 72 | ~ NmapReport: started at 1361737906 hosts up 2/2 elapsed: 0.14 => 134.36 73 | + NmapHost: [74.207.244.221 (scanme.nmap.org scanme.nmap.org) - up] 74 | 75 | Note that, in the above example, lines prefixed with: 76 | 77 | 1. '~' means values changed 78 | 2. '+ means values were added 79 | 3. '-' means values were removed 80 | 81 | NmapDiff methods 82 | ---------------- 83 | 84 | .. automodule:: libnmap.diff 85 | .. autoclass:: NmapDiff 86 | :members: 87 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to libnmap's documentation! 2 | =================================== 3 | 4 | About libnmap 5 | ------------- 6 | 7 | libnmap is a python toolkit for manipulating nmap. It currently offers the following modules: 8 | 9 | - process: enables you to launch nmap scans 10 | - parse: enables you to parse nmap reports or scan results (only XML so far) from a file, a string,... 11 | - report: enables you to manipulate a parsed scan result and de/serialize scan results in a json format 12 | - diff: enables you to see what changed between two scans 13 | - objects: contains basic nmap objects like NmapHost and NmapService. It is to note that each object can be "diff()ed" with another similar object. 14 | 15 | - report: contains NmapReport class definition 16 | - host: contains NmapHost class definition 17 | - service: contains NmapService class definition 18 | - os: contains NmapOSFingerprint class definition and some other classes like NmapOSMatch, NmapOSClass,... 19 | - cpe: contains CPE class defdinition 20 | 21 | - plugins: enables you to support datastores for your scan results directly in the "NmapReport" object from report module 22 | 23 | - mongodb: only plugin implemented so far, ultra basic, for POC purpose only 24 | - sqlalchemy: Allow to store/retrieve NmapReport to sqlite/mysql/... all engine supported by sqlalchemy 25 | - rabbitMQ : todo 26 | - couchdb: todo 27 | - elastic search: todo 28 | - csv: todo 29 | 30 | 31 | libnmap's modules 32 | ----------------- 33 | 34 | The full `source code `_ is available on GitHub. Please, do not hesitate to fork it and issue pull requests. 35 | 36 | The different modules are documented below: 37 | 38 | .. toctree:: 39 | :maxdepth: 2 40 | :glob: 41 | 42 | process 43 | parser 44 | objects 45 | objects/* 46 | diff 47 | plugins_s3 48 | 49 | Indices and tables 50 | ================== 51 | 52 | * :ref:`genindex` 53 | * :ref:`modindex` 54 | * :ref:`search` 55 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\libnmap.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\libnmap.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/objects.rst: -------------------------------------------------------------------------------- 1 | libnmap.objects 2 | =============== 3 | 4 | Using libnmap.objects module 5 | ---------------------------- 6 | 7 | This module contains the definition and API of all "NmapObjects" which enables user to manipulate nmap data: 8 | 9 | 1. NmapReport 10 | 2. NmapHost 11 | 3. NmapService 12 | 13 | The three objects above are the most common one that one would manipulate. For more advanced usage, the following objects might be useful 14 | 15 | 1. NmapOSFingerprint (contains: NmapOSMatch, NmapOSClass, OSFPPortUsed) 16 | 2. CPE (Common platform enumeration contained in NmapService or NmapOSClass) 17 | 18 | The following structure applies by default: 19 | 20 | NmapReport contains: 21 | - Scan "header" data (start time, nmap command, nmap version, ...) 22 | - List of NmapHosts (0 to X scanned hosts could be nested in a nmap report) 23 | - Scan "footer" data (end time, summary, ...) 24 | 25 | NmapHost contains: 26 | - Host "header" data (state, hostnames, ip, ...) 27 | - List of NmapService (0 to X scanned services could be nested in a scanned host) 28 | - Host "footer" data (os version, fingerprint, uptime, ...) 29 | 30 | NmapService contains: 31 | - scan results for this service: 32 | - service state, service name 33 | - optional: service banner 34 | - optional: NSE scripts data 35 | 36 | Each of the above-mentioned objects have a diff() method which enables the user of the lib the compare two different objects 37 | of the same type. 38 | If you read the code you'll see the dirty trick with id() which ensures that proper objects are being compared. The logic of diff will certainly change overtime but the API (i/o) will be kept as is. 39 | 40 | For more info on diff, please check the module's `documentation _`. 41 | -------------------------------------------------------------------------------- /docs/objects/cpe.rst: -------------------------------------------------------------------------------- 1 | libnmap.objects.cpe 2 | =================== 3 | 4 | Using libnmap.objects.cpe module 5 | -------------------------------- 6 | 7 | TODO 8 | 9 | CPE methods 10 | ----------- 11 | 12 | .. automodule:: libnmap.objects.cpe 13 | .. autoclass:: CPE 14 | :members: 15 | -------------------------------------------------------------------------------- /docs/objects/nmaphost.rst: -------------------------------------------------------------------------------- 1 | libnmap.objects.host 2 | ==================== 3 | 4 | Using libnmap.objects.host module 5 | --------------------------------- 6 | 7 | TODO 8 | 9 | NmapHost methods 10 | ---------------- 11 | 12 | .. automodule:: libnmap.objects 13 | .. autoclass:: NmapHost 14 | :members: 15 | -------------------------------------------------------------------------------- /docs/objects/nmapreport.rst: -------------------------------------------------------------------------------- 1 | libnmap.objects.report 2 | ====================== 3 | 4 | Using libnmap.objects.report module 5 | ----------------------------------- 6 | 7 | TODO 8 | 9 | NmapReport methods 10 | ------------------ 11 | 12 | .. automodule:: libnmap.objects 13 | .. autoclass:: NmapReport 14 | :members: 15 | -------------------------------------------------------------------------------- /docs/objects/nmapservice.rst: -------------------------------------------------------------------------------- 1 | libnmap.objects.service 2 | ======================= 3 | 4 | Using libnmap.objects.service module 5 | ------------------------------------ 6 | 7 | TODO 8 | 9 | NmapService methods 10 | ------------------- 11 | 12 | .. automodule:: libnmap.objects 13 | .. autoclass:: NmapService 14 | :members: 15 | -------------------------------------------------------------------------------- /docs/objects/os.rst: -------------------------------------------------------------------------------- 1 | libnmap.objects.os 2 | ================== 3 | 4 | Using libnmap.objects.os module 5 | ------------------------------- 6 | 7 | TODO 8 | 9 | NmapOSFingerprint methods 10 | ------------------------- 11 | 12 | .. automodule:: libnmap.objects.os 13 | .. autoclass:: NmapOSFingerprint 14 | :members: 15 | 16 | NmapOSMatch methods 17 | ------------------- 18 | 19 | .. autoclass:: NmapOSMatch 20 | :members: 21 | 22 | NmapOSClass methods 23 | ------------------- 24 | 25 | .. autoclass:: NmapOSClass 26 | :members: 27 | 28 | OSFPPortUsed methods 29 | -------------------- 30 | 31 | .. autoclass:: OSFPPortUsed 32 | :members: 33 | -------------------------------------------------------------------------------- /docs/parser.rst: -------------------------------------------------------------------------------- 1 | libnmap.parser 2 | ============== 3 | 4 | Security note for libnmap.parser 5 | -------------------------------- 6 | 7 | **TLDR:** if you are importing/parsing untrusted XML scan outputs with python-libnmap, install defusedxml library: 8 | 9 | .. code-block:: bash 10 | 11 | ronald@brouette:~/dev$ pip install defusedxml 12 | 13 | By default, python-libnmap's parser module does not enforces an extra XML parser module than the one provided in the python core distribution. 14 | 15 | In versions previous to 0.7.2, by default, the `ElementTree XML API was used `_. 16 | This XML library is vulnerable to several `XML External Entities attacks `_ which may lead to: 17 | 18 | - Denial of Service attacks 19 | - Remote and local files inclusions 20 | - Remote code execution 21 | 22 | This implies, de facto, that parsing any untrusted XML file could result in any of the above. 23 | 24 | Fortunately, one of the python core developer is maintaining an alternative Python XML parsing library: `defusedxml `_ which addresses all the above vulnerabilities. 25 | 26 | Since the above vulnerabilities will only affect you if you are parsing untrusted XML scan outputs, by default, the defusedxml library is not enforced. 27 | But if the defusedxml library is installed, it will be the preferred XML parser picked by python-libnmap. 28 | 29 | Consider the following lines from libnmap.parser module: 30 | 31 | .. literalinclude:: ../libnmap/parser.py 32 | :linenos: 33 | :lines: 3-10 34 | 35 | 36 | - Line 4 first tries to import defusedxml 37 | - if it fails, it then tries to load cElementTree (known to be more performant) 38 | - if it fails, it then defaults to XML ElementTree. 39 | 40 | Purpose of libnmap.parser 41 | ------------------------- 42 | 43 | This modules enables you to parse nmap scans' output. For now on, only XML parsing is supported. NmapParser is a factory which will return a NmapReport, NmapHost or NmapService object. 44 | All these objects' API are documented. 45 | 46 | The module is capable of parsing: 47 | 48 | - a complete nmap XML scan report 49 | - an incomplete/interrupted nmap XML scan report 50 | - partial nmap xml tags: , and 51 | 52 | Input the above capabilities could be either a string or a file path. 53 | 54 | Based on the provided data, NmapParse.parse() could return the following: 55 | 56 | - NmapReport object: in case a full nmap xml/dict report was prodivded 57 | - NmapHost object: in case a nmap xml section was provided 58 | - NmapService object: in case a nmap xml section was provided 59 | - Python dict with following keys: ports and extraports; python lists. 60 | 61 | Using libnmap.parser module 62 | --------------------------- 63 | 64 | NmapParser parse the whole data and returns nmap objects usable via their documented API. 65 | 66 | The NmapParser should never be instantiated and only the following methods should be called: 67 | 68 | - NmapParser.parse(string) 69 | - NmapParser.parse_fromfile(file_path) 70 | - NmapParser.parse_fromstring(string) 71 | 72 | All of the above methods can receive as input: 73 | 74 | - a full XML nmap scan result and returns a NmapReport object 75 | - a scanned host in XML (... tag) and will return a NmapHost object 76 | - a list of scanned services in XML (... tag) and will return a python array of NmapService objects 77 | - a scanned service in XML (... tag) and will return a NmapService object 78 | 79 | Small example: 80 | 81 | .. code-block:: python 82 | 83 | from libnmap.parser import NmapParser 84 | 85 | nmap_report = NmapParser.parse_fromfile('libnmap/test/files/1_os_banner_scripts.xml') 86 | print "Nmap scan summary: {0}".format(nmap_report.summary) 87 | 88 | Basic usage from a processed scan: 89 | 90 | .. code-block:: python 91 | 92 | from libnmap.process import NmapProcess 93 | from libnmap.parser import NmapParser 94 | 95 | nm = NmapProcess("127.0.0.1, scanme.nmap.org") 96 | nm.run() 97 | 98 | nmap_report = NmapParser.parse(nm.stdout) 99 | 100 | for scanned_hosts in nmap_report.hosts: 101 | print scanned_hosts 102 | 103 | For more details on using the results from NmapParser, refer to the API of class: NmapReport, NmapHost, NmapService. 104 | 105 | NmapParser methods 106 | ------------------ 107 | 108 | .. automodule:: libnmap.parser 109 | .. autoclass:: NmapParser 110 | :members: 111 | -------------------------------------------------------------------------------- /docs/plugins_s3.rst: -------------------------------------------------------------------------------- 1 | libnmap.plugins.s3.NmapS3Plugin 2 | =============================== 3 | 4 | Using libnmap.plugins.s3 5 | ------------------------ 6 | 7 | This modules enables the user to directly use S3 buckets to store and retrieve NmapReports. 8 | 9 | NmapS3Plugin methods 10 | -------------------- 11 | 12 | .. automodule:: libnmap.plugins.s3 13 | .. autoclass:: NmapS3Plugin 14 | :members: 15 | -------------------------------------------------------------------------------- /docs/process.rst: -------------------------------------------------------------------------------- 1 | libnmap.process 2 | =============== 3 | 4 | Purpose of libnmap.process 5 | -------------------------- 6 | 7 | The purpose of this module is to enable the lib users to launch and control nmap scans. This module will consequently fire the nmap command following the specified parameters provided in the constructor. 8 | 9 | It is to note that this module will not perform a full inline parsing of the data. Only specific events are parsed and exploitable via either a callback function defined by the user and provided in the constructor or by running the process in the background and accessing the NmapProcess attributes while the scan is running. 10 | 11 | To run an nmap scan, you need to: 12 | 13 | - instantiate NmapProcess 14 | - call the run*() methods 15 | 16 | Raw results of the scans will be available in the following properties: 17 | 18 | - NmapProcess.stdout: string, XML output 19 | - NmapProcess.stderr: string, text error message from nmap process 20 | 21 | To instantiate an NmapProcess instance, call the constructor with the appropriate parameters 22 | 23 | Processing of events 24 | -------------------- 25 | 26 | While Nmap is running, some events are processed and parsed. This would enable you to: 27 | 28 | - evaluate estimated time to completion and progress in percentage 29 | - find out which task is running and how many nmap tasks have been executed 30 | - know the start time and nmap version 31 | 32 | As you may know, depending on the nmap options you specified, nmap will execute several tasks like "DNS Resolve", "Ping Scan", "Connect Scan", "NSE scripts",... This is of course independent from libnmap but the lib is able to parse these tasks and will instantiate a NmapTask object for any task executed. The list of executed task is available via the following properties: 33 | 34 | - NmapProcess.tasks: list of NmapTask object (executed nmap tasks) 35 | - NmapProcess.current_task: returns the currently running NmapTask 36 | 37 | You will find below the list of attributes you can use when dealing with NmapTask: 38 | 39 | - name: task name (check nmap documentation for the complete list) 40 | - etc: unix timestamp of estimated time to completion 41 | - progress: estimated percentage of task completion 42 | - percent: estimated percentage of task completion (same as progress) 43 | - remaining: estimated number of seconds to completion 44 | - status: status of the task ('started' or 'ended') 45 | - starttime: unix timestamp of when the task started 46 | - endtime: unix timestamp of when the task ended, 0 if not completed yet 47 | - extrainfo: extra information stored for specific tasks 48 | - updated: unix timestamp of last data update for this task 49 | 50 | Using libnmap.process 51 | --------------------- 52 | 53 | This modules enables you to launch nmap scans with simples python commands:: 54 | 55 | from libnmap.process import NmapProcess 56 | 57 | nm = NmapProcess("scanme.nmap.org", options="-sV") 58 | rc = nm.run() 59 | 60 | if nm.rc == 0: 61 | print nm.stdout 62 | else: 63 | print nm.stderr 64 | 65 | This module is also able to trigger a callback function provided by the user. This callback will be triggered each time nmap returns data to the lib. 66 | It is to note that the lib forces nmap to return its status (progress and etc) every two seconds. The event callback could then play around with those values while running. 67 | 68 | To go a bit further, you can always use the threading capabilities of the NmapProcess class and run the class in the background 69 | 70 | .. literalinclude:: ../examples/proc_async.py 71 | 72 | The above code will print out the following on standard output:: 73 | 74 | (pydev)[dev@bouteille python-nmap-lib]$ python examples/proc_async.py 75 | Nmap Scan running: ETC: 0 DONE: 0% 76 | Nmap Scan running: ETC: 1369433951 DONE: 2.45% 77 | Nmap Scan running: ETC: 1369433932 DONE: 13.55% 78 | Nmap Scan running: ETC: 1369433930 DONE: 25.35% 79 | Nmap Scan running: ETC: 1369433931 DONE: 33.40% 80 | Nmap Scan running: ETC: 1369433932 DONE: 41.50% 81 | Nmap Scan running: ETC: 1369433931 DONE: 52.90% 82 | Nmap Scan running: ETC: 1369433931 DONE: 62.55% 83 | Nmap Scan running: ETC: 1369433930 DONE: 75.55% 84 | Nmap Scan running: ETC: 1369433931 DONE: 81.35% 85 | Nmap Scan running: ETC: 1369433931 DONE: 99.99% 86 | rc: 0 output: Nmap done at Sat May 25 00:18:51 2013; 1 IP address (1 host up) scanned in 22.02 seconds 87 | (pydev)[dev@bouteille python-nmap-lib]$ 88 | 89 | Another and last example of a simple use of the NmapProcess class. The code below prints out the scan results a la nmap 90 | 91 | .. literalinclude:: ../examples/proc_nmap_like.py 92 | 93 | The above code will print out the following on standard output:: 94 | 95 | (pydev)[dev@bouteille python-nmap-lib]$ python examples/proc_nmap_like.py 96 | Starting Nmap 5.51 ( http://nmap.org ) at Sat May 25 00:14:54 2013 97 | Nmap scan report for localhost (127.0.0.1) 98 | Host is up. 99 | PORT STATE SERVICE 100 | 22/tcp open ssh (product: OpenSSH extrainfo: protocol 2.0 version: 5.3) 101 | 25/tcp open smtp (product: Postfix smtpd hostname: bouteille.localdomain) 102 | 80/tcp open http (product: nginx version: 1.0.15) 103 | 111/tcp open rpcbind (version: 2-4 extrainfo: rpc #100000) 104 | 631/tcp open ipp (product: CUPS version: 1.4) 105 | Nmap done at Sat May 25 00:15:00 2013; 1 IP address (1 host up) scanned in 6.25 seconds 106 | (pydev)[dev@bouteille python-nmap-lib]$ 107 | 108 | The full `source code `_ is available on GitHub. Please, do not hesitate to fork it and issue pull requests. 109 | 110 | NmapProcess methods 111 | ------------------- 112 | 113 | .. automodule:: libnmap.process 114 | .. autoclass:: NmapProcess 115 | :members: 116 | 117 | .. automethod:: __init__ 118 | 119 | NmapTask methods 120 | ------------------- 121 | 122 | .. autoclass:: NmapTask 123 | :members: 124 | -------------------------------------------------------------------------------- /examples/check_cpe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from libnmap.parser import NmapParser 5 | 6 | rep = NmapParser.parse_fromfile("libnmap/test/files/full_sudo6.xml") 7 | 8 | print( 9 | "Nmap scan discovered {0}/{1} hosts up".format( 10 | rep.hosts_up, rep.hosts_total 11 | ) 12 | ) 13 | for _host in rep.hosts: 14 | if _host.is_up(): 15 | print( 16 | "+ Host: {0} {1}".format(_host.address, " ".join(_host.hostnames)) 17 | ) 18 | 19 | # get CPE from service if available 20 | for s in _host.services: 21 | print( 22 | " Service: {0}/{1} ({2})".format( 23 | s.port, s.protocol, s.state 24 | ) 25 | ) 26 | # NmapService.cpelist returns an array of CPE objects 27 | for _serv_cpe in s.cpelist: 28 | print(" CPE: {0}".format(_serv_cpe.cpestring)) 29 | 30 | if _host.os_fingerprinted: 31 | print(" OS Fingerprints") 32 | for osm in _host.os.osmatches: 33 | print( 34 | " Found Match:{0} ({1}%)".format(osm.name, osm.accuracy) 35 | ) 36 | # NmapOSMatch.get_cpe() method return an array of string 37 | # unlike NmapOSClass.cpelist which returns an array of CPE obj 38 | for cpe in osm.get_cpe(): 39 | print("\t CPE: {0}".format(cpe)) 40 | -------------------------------------------------------------------------------- /examples/diff_sample1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from libnmap.parser import NmapParser 5 | 6 | rep1 = NmapParser.parse_fromfile("libnmap/test/files/1_hosts.xml") 7 | rep2 = NmapParser.parse_fromfile("libnmap/test/files/1_hosts_diff.xml") 8 | 9 | rep1_items_changed = rep1.diff(rep2).changed() 10 | changed_host_id = rep1_items_changed.pop().split("::")[1] 11 | 12 | changed_host1 = rep1.get_host_byid(changed_host_id) 13 | changed_host2 = rep2.get_host_byid(changed_host_id) 14 | host1_items_changed = changed_host1.diff(changed_host2).changed() 15 | 16 | changed_service_id = host1_items_changed.pop().split("::")[1] 17 | changed_service1 = changed_host1.get_service_byid(changed_service_id) 18 | changed_service2 = changed_host2.get_service_byid(changed_service_id) 19 | service1_items_changed = changed_service1.diff(changed_service2).changed() 20 | 21 | for diff_attr in service1_items_changed: 22 | print( 23 | "diff({0}, {1}) [{2}:{3}] [{4}:{5}]".format( 24 | changed_service1.id, 25 | changed_service2.id, 26 | diff_attr, 27 | getattr(changed_service1, diff_attr), 28 | diff_attr, 29 | getattr(changed_service2, diff_attr), 30 | ) 31 | ) 32 | -------------------------------------------------------------------------------- /examples/diff_sample2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from libnmap.parser import NmapParser 5 | 6 | 7 | def nested_obj(objname): 8 | rval = None 9 | splitted = objname.split("::") 10 | if len(splitted) == 2: 11 | rval = splitted 12 | return rval 13 | 14 | 15 | def print_diff_added(obj1, obj2, added): 16 | for akey in added: 17 | nested = nested_obj(akey) 18 | if nested is not None: 19 | if nested[0] == "NmapHost": 20 | subobj1 = obj1.get_host_byid(nested[1]) 21 | elif nested[0] == "NmapService": 22 | subobj1 = obj1.get_service_byid(nested[1]) 23 | print("+ {0}".format(subobj1)) 24 | else: 25 | print("+ {0} {1}: {2}".format(obj1, akey, getattr(obj1, akey))) 26 | 27 | 28 | def print_diff_removed(obj1, obj2, removed): 29 | for rkey in removed: 30 | nested = nested_obj(rkey) 31 | if nested is not None: 32 | if nested[0] == "NmapHost": 33 | subobj2 = obj2.get_host_byid(nested[1]) 34 | elif nested[0] == "NmapService": 35 | subobj2 = obj2.get_service_byid(nested[1]) 36 | print("- {0}".format(subobj2)) 37 | else: 38 | print("- {0} {1}: {2}".format(obj2, rkey, getattr(obj2, rkey))) 39 | 40 | 41 | def print_diff_changed(obj1, obj2, changes): 42 | for mkey in changes: 43 | nested = nested_obj(mkey) 44 | if nested is not None: 45 | if nested[0] == "NmapHost": 46 | subobj1 = obj1.get_host_byid(nested[1]) 47 | subobj2 = obj2.get_host_byid(nested[1]) 48 | elif nested[0] == "NmapService": 49 | subobj1 = obj1.get_service_byid(nested[1]) 50 | subobj2 = obj2.get_service_byid(nested[1]) 51 | print_diff(subobj1, subobj2) 52 | else: 53 | print( 54 | "~ {0} {1}: {2} => {3}".format( 55 | obj1, mkey, getattr(obj2, mkey), getattr(obj1, mkey) 56 | ) 57 | ) 58 | 59 | 60 | def print_diff(obj1, obj2): 61 | ndiff = obj1.diff(obj2) 62 | 63 | print_diff_changed(obj1, obj2, ndiff.changed()) 64 | print_diff_added(obj1, obj2, ndiff.added()) 65 | print_diff_removed(obj1, obj2, ndiff.removed()) 66 | 67 | 68 | def main(): 69 | newrep = NmapParser.parse_fromfile( 70 | "libnmap/test/files/2_hosts_achange.xml" 71 | ) 72 | oldrep = NmapParser.parse_fromfile("libnmap/test/files/1_hosts.xml") 73 | 74 | print_diff(newrep, oldrep) 75 | 76 | 77 | if __name__ == "__main__": 78 | main() 79 | -------------------------------------------------------------------------------- /examples/elastikibana.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from datetime import datetime 5 | 6 | import pygeoip 7 | from elasticsearch import Elasticsearch 8 | 9 | from libnmap.parser import NmapParser 10 | 11 | 12 | def store_report(nmap_report, database, index): 13 | rval = True 14 | for nmap_host in nmap_report.hosts: 15 | rv = store_reportitem(nmap_host, database, index) 16 | if rv is False: 17 | print( 18 | "Failed to store host {0} in " 19 | "elasticsearch".format(nmap_host.address) 20 | ) 21 | rval = False 22 | 23 | return rval 24 | 25 | 26 | def get_os(nmap_host): 27 | rval = {"vendor": "unknown", "product": "unknown"} 28 | if nmap_host.is_up() and nmap_host.os_fingerprinted: 29 | cpelist = nmap_host.os.os_cpelist() 30 | if len(cpelist): 31 | mcpe = cpelist.pop() 32 | rval.update( 33 | {"vendor": mcpe.get_vendor(), "product": mcpe.get_product()} 34 | ) 35 | return rval 36 | 37 | 38 | def get_geoip_code(address): 39 | gi = pygeoip.GeoIP("/usr/share/GeoIP/GeoIP.dat") 40 | return gi.country_code_by_addr(address) 41 | 42 | 43 | def store_reportitem(nmap_host, database, index): 44 | host_keys = [ 45 | "starttime", 46 | "endtime", 47 | "address", 48 | "hostnames", 49 | "ipv4", 50 | "ipv6", 51 | "mac", 52 | "status", 53 | ] 54 | jhost = {} 55 | for hkey in host_keys: 56 | if hkey == "starttime" or hkey == "endtime": 57 | val = getattr(nmap_host, hkey) 58 | jhost[hkey] = datetime.fromtimestamp(int(val) if len(val) else 0) 59 | else: 60 | jhost[hkey] = getattr(nmap_host, hkey) 61 | 62 | jhost.update({"country": get_geoip_code(nmap_host.address)}) 63 | jhost.update(get_os(nmap_host)) 64 | for nmap_service in nmap_host.services: 65 | reportitems = get_item(nmap_service) 66 | 67 | for ritem in reportitems: 68 | ritem.update(jhost) 69 | database.index(index=index, doc_type="NmapItem", body=ritem) 70 | return jhost 71 | 72 | 73 | def get_item(nmap_service): 74 | service_keys = ["port", "protocol", "state"] 75 | ritems = [] 76 | 77 | # create report item for basic port scan 78 | jservice = {} 79 | for skey in service_keys: 80 | jservice[skey] = getattr(nmap_service, skey) 81 | jservice["type"] = "port-scan" 82 | jservice["service"] = nmap_service.service 83 | jservice["service-data"] = nmap_service.banner 84 | ritems.append(jservice) 85 | 86 | # create report items from nse script output 87 | for nse_item in nmap_service.scripts_results: 88 | jnse = {} 89 | for skey in service_keys: 90 | jnse[skey] = getattr(nmap_service, skey) 91 | jnse["type"] = "nse-script" 92 | jnse["service"] = nse_item["id"] 93 | jnse["service-data"] = nse_item["output"] 94 | ritems.append(jnse) 95 | 96 | return ritems 97 | 98 | 99 | xmlscans = [ 100 | "../libnmap/test/files/1_hosts.xml", 101 | "../libnmap/test/files/full_sudo6.xml", 102 | "/vagrant/nmap_switches.xml", 103 | "/vagrant/nmap-5hosts.xml", 104 | ] 105 | 106 | for xmlscan in xmlscans: 107 | nmap_report = NmapParser.parse_fromfile(xmlscan) 108 | 109 | if nmap_report: 110 | rep_date = datetime.fromtimestamp(int(nmap_report.started)) 111 | index = "nmap-{0}".format(rep_date.strftime("%Y-%m-%d")) 112 | db = Elasticsearch() 113 | j = store_report(nmap_report, db, index) 114 | -------------------------------------------------------------------------------- /examples/es_plugin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import json 4 | from datetime import datetime 5 | 6 | from libnmap.parser import NmapParser 7 | from libnmap.plugins.es import NmapElasticsearchPlugin 8 | from libnmap.reportjson import ReportDecoder 9 | 10 | nmap_report = NmapParser.parse_fromfile("libnmap/test/files/1_hosts.xml") 11 | mindex = datetime.fromtimestamp(nmap_report.started).strftime("%Y-%m-%d") 12 | db = NmapElasticsearchPlugin(index=mindex) 13 | dbid = db.insert(nmap_report) 14 | nmap_json = db.get(dbid) 15 | 16 | nmap_obj = json.loads(json.dumps(nmap_json), cls=ReportDecoder) 17 | print(nmap_obj) 18 | # print(db.getall()) 19 | -------------------------------------------------------------------------------- /examples/json_serialize.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | 6 | from libnmap.parser import NmapParser 7 | from libnmap.reportjson import ReportDecoder, ReportEncoder 8 | 9 | nmap_report_obj = NmapParser.parse_fromfile("libnmap/test/files/1_hosts.xml") 10 | 11 | # create a json object from an NmapReport instance 12 | nmap_report_json = json.dumps(nmap_report_obj, cls=ReportEncoder) 13 | print(nmap_report_json) 14 | # create a NmapReport instance from a json object 15 | nmap_report_obj = json.loads(nmap_report_json, cls=ReportDecoder) 16 | print(nmap_report_obj) 17 | -------------------------------------------------------------------------------- /examples/kibanalibnmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savon-noir/python-libnmap/f9887fc9add90e2e0b3c59671ec5ecd9bee8fcd3/examples/kibanalibnmap.png -------------------------------------------------------------------------------- /examples/nmap_task.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from libnmap.process import NmapProcess 5 | 6 | 7 | def mycallback(nmaptask): 8 | nmaptask = nmap_proc.current_task 9 | if nmaptask: 10 | print( 11 | "Task {0} ({1}): ETC: {2} DONE: {3}%".format( 12 | nmaptask.name, nmaptask.status, nmaptask.etc, nmaptask.progress 13 | ) 14 | ) 15 | 16 | 17 | nmap_proc = NmapProcess( 18 | targets="scanme.nmap.org", options="-sV", event_callback=mycallback 19 | ) 20 | nmap_proc.run() 21 | print(nmap_proc.stdout) 22 | print(nmap_proc.stderr) 23 | -------------------------------------------------------------------------------- /examples/nmap_task_bg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from libnmap.process import NmapProcess 5 | 6 | nmap_proc = NmapProcess(targets="scanme.nmap.org", options="-sV") 7 | nmap_proc.run_background() 8 | while nmap_proc.is_running(): 9 | nmaptask = nmap_proc.current_task 10 | if nmaptask: 11 | print( 12 | "Task {0} ({1}): ETC: {2} DONE: {3}%".format( 13 | nmaptask.name, nmaptask.status, nmaptask.etc, nmaptask.progress 14 | ) 15 | ) 16 | print("rc: {0} output: {1}".format(nmap_proc.rc, nmap_proc.summary)) 17 | print(nmap_proc.stdout) 18 | print(nmap_proc.stderr) 19 | -------------------------------------------------------------------------------- /examples/os_fingerprint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from libnmap.parser import NmapParser 5 | 6 | rep = NmapParser.parse_fromfile("libnmap/test/files/os_scan6.xml") 7 | 8 | print("{0}/{1} hosts up".format(rep.hosts_up, rep.hosts_total)) 9 | for _host in rep.hosts: 10 | if _host.is_up(): 11 | print("{0} {1}".format(_host.address, " ".join(_host.hostnames))) 12 | if _host.os_fingerprinted: 13 | print("OS Fingerprint:") 14 | msg = "" 15 | for osm in _host.os.osmatches: 16 | print("Found Match:{0} ({1}%)".format(osm.name, osm.accuracy)) 17 | for osc in osm.osclasses: 18 | print("\tOS Class: {0}".format(osc.description)) 19 | for cpe in osc.cpelist: 20 | print("\tCPE: {0}".format(cpe.cpestring)) 21 | else: 22 | print("No fingerprint available") 23 | -------------------------------------------------------------------------------- /examples/proc_async.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from time import sleep 5 | 6 | from libnmap.process import NmapProcess 7 | 8 | nmap_proc = NmapProcess(targets="scanme.nmap.org", options="-sT") 9 | nmap_proc.run_background() 10 | while nmap_proc.is_running(): 11 | print( 12 | "Nmap Scan running: ETC: {0} DONE: {1}%".format( 13 | nmap_proc.etc, nmap_proc.progress 14 | ) 15 | ) 16 | sleep(2) 17 | 18 | print("rc: {0} output: {1}".format(nmap_proc.rc, nmap_proc.summary)) 19 | -------------------------------------------------------------------------------- /examples/proc_nmap_like.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from libnmap.parser import NmapParser, NmapParserException 5 | from libnmap.process import NmapProcess 6 | 7 | 8 | # start a new nmap scan on localhost with some specific options 9 | def do_scan(targets, options): 10 | parsed = None 11 | nmproc = NmapProcess(targets, options) 12 | rc = nmproc.run() 13 | if rc != 0: 14 | print("nmap scan failed: {0}".format(nmproc.stderr)) 15 | 16 | try: 17 | parsed = NmapParser.parse(nmproc.stdout) 18 | except NmapParserException as e: 19 | print("Exception raised while parsing scan: {0}".format(e.msg)) 20 | 21 | return parsed 22 | 23 | 24 | # print scan results from a nmap report 25 | def print_scan(nmap_report): 26 | print( 27 | "Starting Nmap {0} ( http://nmap.org ) at {1}".format( 28 | nmap_report.version, nmap_report.started 29 | ) 30 | ) 31 | 32 | for host in nmap_report.hosts: 33 | if len(host.hostnames): 34 | tmp_host = host.hostnames.pop() 35 | else: 36 | tmp_host = host.address 37 | 38 | print("Nmap scan report for {0} ({1})".format(tmp_host, host.address)) 39 | print("Host is {0}.".format(host.status)) 40 | print(" PORT STATE SERVICE") 41 | 42 | for serv in host.services: 43 | pserv = "{0:>5s}/{1:3s} {2:12s} {3}".format( 44 | str(serv.port), serv.protocol, serv.state, serv.service 45 | ) 46 | if len(serv.banner): 47 | pserv += " ({0})".format(serv.banner) 48 | print(pserv) 49 | print(nmap_report.summary) 50 | 51 | 52 | if __name__ == "__main__": 53 | report = do_scan("127.0.0.1", "-sV") 54 | if report: 55 | print_scan(report) 56 | else: 57 | print("No results returned") 58 | -------------------------------------------------------------------------------- /libnmap/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = "Ronald Bister, Mike Boutillier" 4 | __credits__ = ["Ronald Bister", "Mike Boutillier"] 5 | __maintainer__ = "Ronald Bister" 6 | __email__ = "mini.pelle@gmail.com" 7 | __license__ = "Apache 2.0" 8 | __version__ = "0.7.2" 9 | -------------------------------------------------------------------------------- /libnmap/diff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class DictDiffer(object): 5 | """ 6 | Calculate the difference between two dictionaries as: 7 | (1) items added 8 | (2) items removed 9 | (3) keys same in both but changed values 10 | (4) keys same in both and unchanged values 11 | """ 12 | 13 | def __init__(self, current_dict, past_dict): 14 | self.current_dict = current_dict 15 | self.past_dict = past_dict 16 | self.set_current = set(current_dict.keys()) 17 | self.set_past = set(past_dict.keys()) 18 | self.intersect = self.set_current.intersection(self.set_past) 19 | 20 | def added(self): 21 | return self.set_current - self.intersect 22 | 23 | def removed(self): 24 | return self.set_past - self.intersect 25 | 26 | def changed(self): 27 | return set( 28 | o 29 | for o in self.intersect 30 | if self.past_dict[o] != self.current_dict[o] 31 | ) 32 | 33 | def unchanged(self): 34 | return set( 35 | o 36 | for o in self.intersect 37 | if self.past_dict[o] == self.current_dict[o] 38 | ) 39 | 40 | 41 | class NmapDiff(DictDiffer): 42 | """ 43 | NmapDiff compares two objects of same type to enable the user to check: 44 | 45 | - what has changed 46 | - what has been added 47 | - what has been removed 48 | - what was kept unchanged 49 | 50 | NmapDiff inherit from DictDiffer which makes the actual comparison. 51 | The different methods from DictDiffer used by NmapDiff are the 52 | following: 53 | 54 | - NmapDiff.changed() 55 | - NmapDiff.added() 56 | - NmapDiff.removed() 57 | - NmapDiff.unchanged() 58 | 59 | Each of the returns a python set() of key which have changed in the 60 | compared objects. To check the different keys that could be returned, 61 | refer to the get_dict() method of the objects you which to 62 | compare (i.e: libnmap.objects.NmapHost, NmapService,...). 63 | """ 64 | 65 | def __init__(self, nmap_obj1, nmap_obj2): 66 | """ 67 | Constructor of NmapDiff: 68 | 69 | - Checks if the two objects are of the same class 70 | - Checks if the objects are "comparable" via a call to id() (dirty) 71 | - Inherits from DictDiffer and 72 | """ 73 | if ( 74 | nmap_obj1.__class__ != nmap_obj2.__class__ 75 | or nmap_obj1.id != nmap_obj2.id 76 | ): 77 | raise NmapDiffException("Comparing objects with non-matching id") 78 | 79 | self.object1 = nmap_obj1.get_dict() 80 | self.object2 = nmap_obj2.get_dict() 81 | 82 | DictDiffer.__init__(self, self.object1, self.object2) 83 | 84 | def __repr__(self): 85 | return ( 86 | "added: [{0}] -- changed: [{1}] -- " 87 | "unchanged: [{2}] -- removed [{3}]".format( 88 | self.added(), self.changed(), self.unchanged(), self.removed() 89 | ) 90 | ) 91 | 92 | 93 | class NmapDiffException(Exception): 94 | def __init__(self, msg): 95 | self.msg = msg 96 | -------------------------------------------------------------------------------- /libnmap/objects/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from libnmap.objects.host import NmapHost 4 | from libnmap.objects.report import NmapReport 5 | from libnmap.objects.service import NmapService 6 | 7 | __all__ = ["NmapReport", "NmapHost", "NmapService"] 8 | -------------------------------------------------------------------------------- /libnmap/objects/cpe.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class CPE(object): 5 | """ 6 | CPE class offers an API for basic CPE objects. 7 | These objects could be found in NmapService or in tag 8 | within NmapHost. 9 | 10 | :todo: interpret CPE string and provide appropriate API 11 | """ 12 | 13 | def __init__(self, cpestring): 14 | self._cpestring = cpestring 15 | zk = [ 16 | "cpe", 17 | "part", 18 | "vendor", 19 | "product", 20 | "version", 21 | "update", 22 | "edition", 23 | "language", 24 | ] 25 | self._cpedict = dict((k, "") for k in zk) 26 | splitup = cpestring.split(":") 27 | self._cpedict.update(dict(zip(zk, splitup))) 28 | 29 | @property 30 | def cpestring(self): 31 | """ 32 | Accessor for the full CPE string. 33 | """ 34 | return self._cpestring 35 | 36 | @property 37 | def cpedict(self): 38 | """ 39 | Accessor for _cpedict 40 | """ 41 | return self._cpedict 42 | 43 | def __repr__(self): 44 | return self._cpestring 45 | 46 | def get_part(self): 47 | """ 48 | Returns the cpe part (/o, /h, /a) 49 | """ 50 | return self._cpedict["part"] 51 | 52 | def get_vendor(self): 53 | """ 54 | Returns the vendor name 55 | """ 56 | return self._cpedict["vendor"] 57 | 58 | def get_product(self): 59 | """ 60 | Returns the product name 61 | """ 62 | return self._cpedict["product"] 63 | 64 | def get_version(self): 65 | """ 66 | Returns the version of the cpe 67 | """ 68 | return self._cpedict["version"] 69 | 70 | def get_update(self): 71 | """ 72 | Returns the update version 73 | """ 74 | return self._cpedict["update"] 75 | 76 | def get_edition(self): 77 | """ 78 | Returns the cpe edition 79 | """ 80 | return self._cpedict["edition"] 81 | 82 | def get_language(self): 83 | """ 84 | Returns the cpe language 85 | """ 86 | return self._cpedict["language"] 87 | 88 | def is_application(self): 89 | """ 90 | Returns True if cpe describes an application 91 | """ 92 | return self.get_part() == "/a" 93 | 94 | def is_hardware(self): 95 | """ 96 | Returns True if cpe describes a hardware 97 | """ 98 | return self.get_part() == "/h" 99 | 100 | def is_operating_system(self): 101 | """ 102 | Returns True if cpe describes an operating system 103 | """ 104 | return self.get_part() == "/o" 105 | -------------------------------------------------------------------------------- /libnmap/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savon-noir/python-libnmap/f9887fc9add90e2e0b3c59671ec5ecd9bee8fcd3/libnmap/plugins/__init__.py -------------------------------------------------------------------------------- /libnmap/plugins/backendplugin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | class NmapBackendPlugin(object): 5 | """ 6 | Abstract class showing to the minimal implementation for a plugin 7 | All subclass MUST at least implement the following methods 8 | """ 9 | 10 | def __init__(self): 11 | self.dbname = "nmapdb" 12 | self.store = "reports" 13 | 14 | def insert(self, NmapReport): 15 | """ 16 | insert NmapReport in the backend 17 | :param NmapReport: 18 | :return: str the ident of the object in the backend for 19 | future usage 20 | or None 21 | """ 22 | raise NotImplementedError 23 | 24 | def delete(self, id): 25 | """ 26 | delete NmapReport if the backend 27 | :param id: str 28 | """ 29 | raise NotImplementedError 30 | 31 | def get(self, id): 32 | """ 33 | retrieve a NmapReport from the backend 34 | :param id: str 35 | :return: NmapReport 36 | """ 37 | raise NotImplementedError 38 | 39 | def getall(self, filter): 40 | """ 41 | :return: collection of tuple (id,NmapReport) 42 | :param filter: Nice to have implement a filter capability 43 | """ 44 | raise NotImplementedError 45 | -------------------------------------------------------------------------------- /libnmap/plugins/backendpluginFactory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import inspect 3 | import sys 4 | 5 | 6 | class BackendPluginFactory(object): 7 | """ 8 | This is a backend plugin factory a backend instance MUST be 9 | created via the static method create() 10 | ie : mybackend = BackendPluginFactory.create() 11 | """ 12 | 13 | @classmethod 14 | def create(cls, plugin_name="mongodb", **kwargs): 15 | """Import the needed lib and return an object NmapBackendPlugin 16 | representing the backend of your desire. 17 | NmapBackendPlugin is an abstract class, to know what argument 18 | need to be given, review the code of the subclass you need 19 | :param plugin_name: str : name of the py file without .py 20 | :return: NmapBackend (abstract class on top of all plugin) 21 | """ 22 | backendplugin = None 23 | plugin_path = "libnmap.plugins.{0}".format(plugin_name) 24 | __import__(plugin_path) 25 | pluginobj = sys.modules[plugin_path] 26 | pluginclasses = inspect.getmembers(pluginobj, inspect.isclass) 27 | for classname, classobj in pluginclasses: 28 | if inspect.getmodule(classobj).__name__.find(plugin_path) == 0: 29 | try: 30 | backendplugin = classobj(**kwargs) 31 | except Exception as error: 32 | raise Exception( 33 | "Cannot create Backend {0}: {1}".format( 34 | classname, error 35 | ) 36 | ) 37 | return backendplugin 38 | -------------------------------------------------------------------------------- /libnmap/plugins/es.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import json 4 | from datetime import datetime 5 | 6 | from elasticsearch import Elasticsearch 7 | 8 | from libnmap.plugins.backendplugin import NmapBackendPlugin 9 | from libnmap.reportjson import ReportEncoder 10 | 11 | 12 | class NmapElasticsearchPlugin(NmapBackendPlugin): 13 | """ 14 | This class enables the user to store and manipulate nmap reports \ 15 | in a elastic search db. 16 | """ 17 | 18 | def __init__(self, index=None): 19 | if index is None: 20 | self.index = "nmap.{0}".format(datetime.now().strftime("%Y-%m-%d")) 21 | else: 22 | self.index = index 23 | self._esapi = Elasticsearch() 24 | 25 | def insert(self, report, doc_type=None): 26 | """ 27 | insert NmapReport in the backend 28 | :param NmapReport: 29 | :return: str the ident of the object in the backend for 30 | future usage 31 | or None 32 | """ 33 | if doc_type is None: 34 | doc_type = "NmapReport" 35 | j = json.dumps(report, cls=ReportEncoder) 36 | res = self._esapi.index( 37 | index=self.index, doc_type=doc_type, body=json.loads(j) 38 | ) 39 | rc = res["_id"] 40 | return rc 41 | 42 | def delete(self, id): 43 | """ 44 | delete NmapReport if the backend 45 | :param id: str 46 | """ 47 | raise NotImplementedError 48 | 49 | def get(self, id): 50 | """ 51 | retrieve a NmapReport from the backend 52 | :param id: str 53 | :return: NmapReport 54 | """ 55 | res = self._esapi.get(index=self.index, doc_type="NmapReport", id=id) 56 | rc = res["_source"] 57 | return rc 58 | 59 | def getall(self, filter=None): 60 | """ 61 | :return: collection of tuple (id,NmapReport) 62 | :param filter: Nice to have implement a filter capability 63 | """ 64 | rsearch = self._esapi.search( 65 | index=self.index, body={"query": {"match_all": {}}} 66 | ) 67 | print("--------------------") 68 | print(type(rsearch)) 69 | print(rsearch) 70 | print("------------") 71 | -------------------------------------------------------------------------------- /libnmap/plugins/mongodb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import json 3 | 4 | from bson.objectid import ObjectId 5 | from pymongo import MongoClient 6 | 7 | from libnmap.parser import NmapParser 8 | from libnmap.plugins.backendplugin import NmapBackendPlugin 9 | from libnmap.reportjson import ReportEncoder 10 | 11 | 12 | class NmapMongodbPlugin(NmapBackendPlugin): 13 | """ 14 | This class handle the persistence of NmapRepport object in mongodb 15 | Implementation is made using pymongo 16 | Object of this class must be create via the 17 | BackendPluginFactory.create(**url) where url is a named dict like 18 | {'plugin_name': "mongodb"} this dict may receive all the param 19 | MongoClient() support 20 | """ 21 | 22 | def __init__(self, dbname=None, store=None, **kwargs): 23 | NmapBackendPlugin.__init__(self) 24 | if dbname is not None: 25 | self.dbname = dbname 26 | if store is not None: 27 | self.store = store 28 | self.dbclient = MongoClient(**kwargs) 29 | self.collection = self.dbclient[self.dbname][self.store] 30 | 31 | def insert(self, report): 32 | """ 33 | create a json object from an NmapReport instance 34 | :param NmapReport: obj to insert 35 | :return: str id 36 | """ 37 | j = json.dumps(report, cls=ReportEncoder) 38 | try: 39 | oid = self.collection.insert(json.loads(j)) 40 | except Exception as e: 41 | em = "Failed to insert nmap object in MongoDB: {0}".format(e) 42 | raise Exception(em) 43 | return str(oid) 44 | 45 | def get(self, str_report_id=None): 46 | """select a NmapReport by Id 47 | :param str: id 48 | :return: NmapReport object 49 | """ 50 | rid = str_report_id 51 | nmapreport = None 52 | if str_report_id is not None and isinstance(str_report_id, str): 53 | rid = ObjectId(str_report_id) 54 | 55 | if isinstance(rid, ObjectId): 56 | # get a specific report by mongo's id 57 | resultset = self.collection.find({"_id": rid}) 58 | if resultset.count() == 1: 59 | # search by id means only one in the iterator 60 | record = resultset[0] 61 | # remove mongo's id to recreate the NmapReport Obj 62 | del record["_id"] 63 | nmapreport = NmapParser.parse_fromdict(record) 64 | return nmapreport 65 | 66 | def getall(self, dict_filter=None): 67 | """return a list of tuple (id,NmapReport) saved in the backend 68 | TODO : add a filter capability 69 | """ 70 | nmapreportlist = [] 71 | resultset = self.collection.find() 72 | for report in resultset: 73 | oid = report["_id"] 74 | del report["_id"] 75 | nmapreport = NmapParser.parse_fromdict(report) 76 | nmapreportlist.append((oid, nmapreport)) 77 | return nmapreportlist 78 | 79 | def delete(self, report_id=None): 80 | """ 81 | delete an obj from the backend 82 | :param str: id 83 | :return: dict document with result or None 84 | """ 85 | if report_id is not None and isinstance(report_id, str): 86 | return self.collection.remove({"_id": ObjectId(report_id)}) 87 | else: 88 | return self.collection.remove({"_id": report_id}) 89 | -------------------------------------------------------------------------------- /libnmap/plugins/s3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | :mod:`libnmap.plugin.s3` -- S3 Backend Plugin 4 | ============================================= 5 | 6 | .. module:: libnmap.plugin.s3 7 | 8 | :platform: Linux 9 | :synopsis: a plugin is representation of a S3 backend using boto 10 | 11 | .. moduleauthor:: Ronald Bister 12 | .. moduleauthor:: Mike Boutillier 13 | """ 14 | import json 15 | 16 | from boto.exception import S3ResponseError 17 | from boto.s3.bucketlistresultset import bucket_lister 18 | from boto.s3.connection import OrdinaryCallingFormat, S3Connection 19 | from boto.s3.key import Key 20 | from bson.objectid import ObjectId 21 | 22 | from libnmap.parser import NmapParser 23 | from libnmap.plugins.backendplugin import NmapBackendPlugin 24 | from libnmap.reportjson import ReportEncoder 25 | 26 | 27 | class NmapS3Plugin(NmapBackendPlugin): 28 | """ 29 | This plugin save the reports on S3 and compatible. 30 | """ 31 | 32 | def __init__(self, **kwargs): 33 | """ 34 | - create the conn object 35 | - create the bucket (if it doesn't exist) 36 | - if not given, awsaccessKey_nmapreport 37 | - may raise exception (ie in case of conflict bucket name) 38 | - sample : 39 | To connect to walrus: 40 | from libnmap.plugins.backendpluginFactory import 41 | BackendPluginFactory 42 | walrusBackend = 43 | BackendPluginFactory.create( 44 | plugin_name='s3', 45 | host="walrus.ecc.eucalyptus.com", 46 | path="/services/Walrus",port=8773, 47 | is_secure=False, 48 | aws_access_key_id='UU72FLVJCAYRATLXI70YH', 49 | aws_secret_access_key= 50 | 'wFg7gP5YFHjVlxakw1g1uCC8UR2xVW5ax9ErZCut') 51 | To connect to S3: 52 | mybackend_S3 = 53 | BackendPluginFactory.create( 54 | plugin_name='s3', 55 | is_secure=True, 56 | aws_access_key_id='MYACCESSKEY', 57 | aws_secret_access_key='MYSECRET') 58 | """ 59 | NmapBackendPlugin.__init__(self) 60 | try: 61 | calling_format = OrdinaryCallingFormat() 62 | if "bucket" not in kwargs: 63 | self.bucket_name = "".join( 64 | [kwargs["aws_access_key_id"].lower(), "_nmapreport"] 65 | ) 66 | else: 67 | self.bucket_name = kwargs["bucket"] 68 | del kwargs["bucket"] 69 | kwargs["calling_format"] = calling_format 70 | self.conn = S3Connection(**kwargs) 71 | self.bucket = self.conn.lookup(self.bucket_name) 72 | if self.bucket is None: 73 | self.bucket = self.conn.create_bucket(self.bucket_name) 74 | except Exception as e: 75 | raise Exception(e) 76 | 77 | def insert(self, report): 78 | """ 79 | create a json string from an NmapReport instance 80 | and push it to S3 bucket. 81 | 82 | :param NmapReport: obj to insert 83 | :rtype: string 84 | :return: str id 85 | :todo: Add tagging option 86 | """ 87 | try: 88 | oid = ObjectId() 89 | mykey = Key(self.bucket) 90 | mykey.key = str(oid) 91 | strjsonnmapreport = json.dumps(report, cls=ReportEncoder) 92 | mykey.set_contents_from_string(strjsonnmapreport) 93 | except Exception as e: 94 | em = "Failed to add nmap object in s3 bucket: {0}".format(e) 95 | raise Exception(em) 96 | return str(oid) 97 | 98 | def get(self, str_report_id=None): 99 | """ 100 | select a NmapReport by Id. 101 | 102 | :param str: id 103 | :rtype: NmapReport 104 | :return: NmapReport object 105 | """ 106 | nmapreport = None 107 | if str_report_id is not None and isinstance(str_report_id, str): 108 | try: 109 | mykey = Key(self.bucket) 110 | mykey.key = str_report_id 111 | nmapreportjson = json.loads(mykey.get_contents_as_string()) 112 | nmapreport = NmapParser.parse_fromdict(nmapreportjson) 113 | except S3ResponseError: 114 | pass 115 | return nmapreport 116 | 117 | def getall(self, dict_filter=None): 118 | """ 119 | :rtype: List of tuple 120 | :return: list of key/report 121 | :todo: add a filter capability 122 | """ 123 | nmapreportlist = [] 124 | for key in bucket_lister(self.bucket): 125 | if isinstance(key, Key): 126 | nmapreportjson = json.loads(key.get_contents_as_string()) 127 | nmapreport = NmapParser.parse_fromdict(nmapreportjson) 128 | nmapreportlist.append((key.key, nmapreport)) 129 | return nmapreportlist 130 | 131 | def delete(self, report_id=None): 132 | """ 133 | delete an obj from the backend 134 | 135 | :param str: id 136 | :return: dict document with result or None 137 | """ 138 | rcode = None 139 | if report_id is not None and isinstance(report_id, str): 140 | rcode = self.bucket.delete_key(report_id) 141 | return rcode 142 | -------------------------------------------------------------------------------- /libnmap/plugins/sql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import json 3 | from datetime import datetime 4 | 5 | from sqlalchemy import create_engine 6 | from sqlalchemy.ext.declarative import declarative_base 7 | from sqlalchemy.orm import sessionmaker 8 | from sqlalchemy.schema import Column 9 | from sqlalchemy.types import DateTime, Integer, LargeBinary 10 | 11 | from libnmap.plugins.backendplugin import NmapBackendPlugin 12 | from libnmap.reportjson import ReportDecoder, ReportEncoder 13 | 14 | Base = declarative_base() 15 | 16 | 17 | class NmapSqlPlugin(NmapBackendPlugin): 18 | """ 19 | This class handle the persistence of NmapRepport object in SQL backend 20 | Implementation is made using sqlalchemy(0.8.1) 21 | usage : 22 | 23 | #get a nmapReport object 24 | from libnmap.parser import NmapParser 25 | from libnmap.reportjson import ReportDecoder, ReportEncoder 26 | import json 27 | nmap_report_obj = NmapParser.parse_fromfile( 28 | '/home/vagrant/python-nmap-lib/libnmap/test/files/1_hosts.xml') 29 | 30 | #get a backend with in memory sqlite 31 | from libnmap.plugins.backendpluginFactory import BackendPluginFactory 32 | mybackend_mem = BackendPluginFactory.create(plugin_name='sql', 33 | url='sqlite://', 34 | echo=True) 35 | 36 | mybackend_mysql = BackendPluginFactory.create(plugin_name='sql', 37 | url='mysql+mysqldb://scott:tiger@localhost/foo', 38 | echo=True) 39 | mybackend = BackendPluginFactory.create(plugin_name='sql', 40 | url='sqlite:////tmp/reportdb.sql', 41 | echo=True) 42 | #lets save!! 43 | nmap_report_obj.save(mybackend) 44 | mybackend.getall() 45 | mybackend.get(1) 46 | """ 47 | 48 | class Reports(Base): 49 | """ 50 | Embeded class for ORM map NmapReport to a 51 | simple three column table 52 | """ 53 | 54 | __tablename__ = "reports" 55 | 56 | id = Column("report_id", Integer, primary_key=True) 57 | inserted = Column("inserted", DateTime(), default="now") 58 | report_json = Column("report_json", LargeBinary()) 59 | 60 | def __init__(self, obj_NmapReport): 61 | self.inserted = datetime.fromtimestamp(obj_NmapReport.endtime) 62 | dumped_json = json.dumps(obj_NmapReport, cls=ReportEncoder) 63 | self.report_json = bytes(dumped_json.encode("UTF-8")) 64 | 65 | def decode(self): 66 | json_decoded = self.report_json.decode("utf-8") 67 | nmap_report_obj = json.loads(json_decoded, cls=ReportDecoder) 68 | return nmap_report_obj 69 | 70 | def __init__(self, **kwargs): 71 | """ 72 | constructor receive a **kwargs as the **kwargs in the sqlalchemy 73 | create_engine() method (see sqlalchemy docs) 74 | You must add to this **kwargs an 'url' key with the url to your 75 | database 76 | This constructor will : 77 | - create all the necessary obj to discuss with the DB 78 | - create all the mapping(ORM) 79 | 80 | todo : support the : sqlalchemy.engine_from_config 81 | 82 | :param **kwargs: 83 | :raises: ValueError if no url is given, 84 | all exception sqlalchemy can throw 85 | ie sqlite in memory url='sqlite://' echo=True 86 | ie sqlite file on hd url='sqlite:////tmp/reportdb.sql' echo=True 87 | ie mysql url='mysql+mysqldb://scott:tiger@localhost/foo' 88 | """ 89 | NmapBackendPlugin.__init__(self) 90 | self.engine = None 91 | self.url = None 92 | self.Session = sessionmaker() 93 | 94 | if "url" not in kwargs: 95 | raise ValueError 96 | self.url = kwargs["url"] 97 | del kwargs["url"] 98 | try: 99 | self.engine = create_engine(self.url, **kwargs) 100 | Base.metadata.create_all(bind=self.engine, checkfirst=True) 101 | self.Session.configure(bind=self.engine) 102 | except Exception as e: 103 | raise (e) 104 | 105 | def insert(self, nmap_report): 106 | """ 107 | insert NmapReport in the backend 108 | 109 | :param NmapReport: 110 | 111 | :returns: the ident of the object in the backend for future usage \ 112 | or None 113 | """ 114 | sess = self.Session() 115 | report = NmapSqlPlugin.Reports(nmap_report) 116 | sess.add(report) 117 | sess.commit() 118 | reportid = report.id 119 | sess.close() 120 | return reportid if reportid else None 121 | 122 | def get(self, report_id=None): 123 | """ 124 | retrieve a NmapReport from the backend 125 | 126 | :param id: str 127 | 128 | :returns: NmapReport 129 | """ 130 | if report_id is None: 131 | raise ValueError 132 | sess = self.Session() 133 | orp = sess.query(NmapSqlPlugin.Reports).filter_by(id=report_id) 134 | our_report = orp.first() 135 | sess.close() 136 | return our_report.decode() if our_report else None 137 | 138 | def getall(self): 139 | """ 140 | :param filter: Nice to have implement a filter capability 141 | 142 | :returns: collection of tuple (id,NmapReport) 143 | """ 144 | sess = self.Session() 145 | nmapreportList = [] 146 | for report in sess.query(NmapSqlPlugin.Reports).order_by( 147 | NmapSqlPlugin.Reports.inserted 148 | ): 149 | nmapreportList.append((report.id, report.decode())) 150 | sess.close() 151 | return nmapreportList 152 | 153 | def delete(self, report_id=None): 154 | """ 155 | Remove a report from the backend 156 | 157 | :param id: str 158 | 159 | :returns: The number of rows deleted 160 | """ 161 | if report_id is None: 162 | raise ValueError 163 | nb_line = 0 164 | sess = self.Session() 165 | rpt = sess.query(NmapSqlPlugin.Reports).filter_by(id=report_id) 166 | nb_line = rpt.delete() 167 | sess.commit() 168 | sess.close() 169 | return nb_line 170 | -------------------------------------------------------------------------------- /libnmap/reportjson.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | 6 | from libnmap.objects import NmapHost, NmapReport, NmapService 7 | from libnmap.objects.os import ( 8 | CPE, 9 | NmapOSClass, 10 | NmapOSFingerprint, 11 | NmapOSMatch, 12 | OSFPPortUsed, 13 | ) 14 | from libnmap.parser import NmapParser 15 | 16 | 17 | class ReportEncoder(json.JSONEncoder): 18 | """ 19 | ReportEncoder is a internal class used mostly by plugins to convert 20 | NmapReport objects in json format. 21 | e.g.: 22 | nmapreport_obj = NmapParser.parse_fromfile( 23 | "libnmap/test/files/1_hosts.xml" 24 | ) 25 | nmapreport_json = json.dumps(nmapreport_obj, cls=ReportEncoder) 26 | """ 27 | 28 | def default(self, obj): 29 | otype = { 30 | "NmapHost": NmapHost, 31 | "NmapOSFingerprint": NmapOSFingerprint, 32 | "NmapOSMatch": NmapOSMatch, 33 | "NmapOSClass": NmapOSClass, 34 | "CPE": CPE, 35 | "OSFPPortUsed": OSFPPortUsed, 36 | "NmapService": NmapService, 37 | "NmapReport": NmapReport, 38 | } 39 | if isinstance(obj, tuple(otype.values())): 40 | key = ("__{0}__").format(obj.__class__.__name__) 41 | return {key: obj.__dict__} 42 | return json.JSONEncoder.default(self, obj) 43 | 44 | 45 | class ReportDecoder(json.JSONDecoder): 46 | """ 47 | ReportDecoder is a internal class used mostly by plugins to convert 48 | json nmap report in to NmapReport objects. 49 | e.g.: 50 | nmap_report_obj = json.loads(nmap_report_json, cls=ReportDecoder) 51 | """ 52 | 53 | def decode(self, json_str): 54 | r = NmapParser.parse_fromdict(json.loads(json_str)) 55 | return r 56 | -------------------------------------------------------------------------------- /libnmap/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savon-noir/python-libnmap/f9887fc9add90e2e0b3c59671ec5ecd9bee8fcd3/libnmap/test/__init__.py -------------------------------------------------------------------------------- /libnmap/test/files/1_host_ping.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /libnmap/test/files/1_hosts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /libnmap/test/files/1_hosts_banner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /libnmap/test/files/1_hosts_banner_ports.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /libnmap/test/files/1_hosts_banner_ports_notsyn.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /libnmap/test/files/1_hosts_banner_ports_xmas.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /libnmap/test/files/1_hosts_diff.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /libnmap/test/files/1_hosts_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /libnmap/test/files/1_hosts_nohostname.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /libnmap/test/files/2_hosts.json: -------------------------------------------------------------------------------- 1 | {"__NmapReport__": {"_nmaprun": {"scanner": "nmap", "args": "nmap -sS -vv -oX 2_hosts.xml localhost scanme.nmap.org", "start": "1361737906", "startstr": "Sun Feb 24 21:31:46 2013", "version": "5.51", "xmloutputversion": "1.03"}, "_scaninfo": {"type": "syn", "protocol": "tcp", "numservices": "1000", "services": "1,3-4,6-7,9,13,17,19-26,30,32-33,37,42-43,49,53,70,79-85,88-90,99-100,106,109-111,113,119,125,135,139,143-144,146,161,163,179,199,211-212,222,254-256,259,264,280,301,306,311,340,366,389,406-407,416-417,425,427,443-445,458,464-465,481,497,500,512-515,524,541,543-545,548,554-555,563,587,593,616-617,625,631,636,646,648,666-668,683,687,691,700,705,711,714,720,722,726,749,765,777,783,787,800-801,808,843,873,880,888,898,900-903,911-912,981,987,990,992-993,995,999-1002,1007,1009-1011,1021-1100,1102,1104-1108,1110-1114,1117,1119,1121-1124,1126,1130-1132,1137-1138,1141,1145,1147-1149,1151-1152,1154,1163-1166,1169,1174-1175,1183,1185-1187,1192,1198-1199,1201,1213,1216-1218,1233-1234,1236,1244,1247-1248,1259,1271-1272,1277,1287,1296,1300-1301,1309-1311,1322,1328,1334,1352,1417,1433-1434,1443,1455,1461,1494,1500-1501,1503,1521,1524,1533,1556,1580,1583,1594,1600,1641,1658,1666,1687-1688,1700,1717-1721,1723,1755,1761,1782-1783,1801,1805,1812,1839-1840,1862-1864,1875,1900,1914,1935,1947,1971-1972,1974,1984,1998-2010,2013,2020-2022,2030,2033-2035,2038,2040-2043,2045-2049,2065,2068,2099-2100,2103,2105-2107,2111,2119,2121,2126,2135,2144,2160-2161,2170,2179,2190-2191,2196,2200,2222,2251,2260,2288,2301,2323,2366,2381-2383,2393-2394,2399,2401,2492,2500,2522,2525,2557,2601-2602,2604-2605,2607-2608,2638,2701-2702,2710,2717-2718,2725,2800,2809,2811,2869,2875,2909-2910,2920,2967-2968,2998,3000-3001,3003,3005-3007,3011,3013,3017,3030-3031,3052,3071,3077,3128,3168,3211,3221,3260-3261,3268-3269,3283,3300-3301,3306,3322-3325,3333,3351,3367,3369-3372,3389-3390,3404,3476,3493,3517,3527,3546,3551,3580,3659,3689-3690,3703,3737,3766,3784,3800-3801,3809,3814,3826-3828,3851,3869,3871,3878,3880,3889,3905,3914,3918,3920,3945,3971,3986,3995,3998,4000-4006,4045,4111,4125-4126,4129,4224,4242,4279,4321,4343,4443-4446,4449,4550,4567,4662,4848,4899-4900,4998,5000-5004,5009,5030,5033,5050-5051,5054,5060-5061,5080,5087,5100-5102,5120,5190,5200,5214,5221-5222,5225-5226,5269,5280,5298,5357,5405,5414,5431-5432,5440,5500,5510,5544,5550,5555,5560,5566,5631,5633,5666,5678-5679,5718,5730,5800-5802,5810-5811,5815,5822,5825,5850,5859,5862,5877,5900-5904,5906-5907,5910-5911,5915,5922,5925,5950,5952,5959-5963,5987-5989,5998-6007,6009,6025,6059,6100-6101,6106,6112,6123,6129,6156,6346,6389,6502,6510,6543,6547,6565-6567,6580,6646,6666-6669,6689,6692,6699,6779,6788-6789,6792,6839,6881,6901,6969,7000-7002,7004,7007,7019,7025,7070,7100,7103,7106,7200-7201,7402,7435,7443,7496,7512,7625,7627,7676,7741,7777-7778,7800,7911,7920-7921,7937-7938,7999-8002,8007-8011,8021-8022,8031,8042,8045,8080-8090,8093,8099-8100,8180-8181,8192-8194,8200,8222,8254,8290-8292,8300,8333,8383,8400,8402,8443,8500,8600,8649,8651-8652,8654,8701,8800,8873,8888,8899,8994,9000-9003,9009-9011,9040,9050,9071,9080-9081,9090-9091,9099-9103,9110-9111,9200,9207,9220,9290,9415,9418,9485,9500,9502-9503,9535,9575,9593-9595,9618,9666,9876-9878,9898,9900,9917,9929,9943-9944,9968,9998-10004,10009-10010,10012,10024-10025,10082,10180,10215,10243,10566,10616-10617,10621,10626,10628-10629,10778,11110-11111,11967,12000,12174,12265,12345,13456,13722,13782-13783,14000,14238,14441-14442,15000,15002-15004,15660,15742,16000-16001,16012,16016,16018,16080,16113,16992-16993,17877,17988,18040,18101,18988,19101,19283,19315,19350,19780,19801,19842,20000,20005,20031,20221-20222,20828,21571,22939,23502,24444,24800,25734-25735,26214,27000,27352-27353,27355-27356,27715,28201,30000,30718,30951,31038,31337,32768-32785,33354,33899,34571-34573,35500,38292,40193,40911,41511,42510,44176,44442-44443,44501,45100,48080,49152-49161,49163,49165,49167,49175-49176,49400,49999-50003,50006,50300,50389,50500,50636,50800,51103,51493,52673,52822,52848,52869,54045,54328,55055-55056,55555,55600,56737-56738,57294,57797,58080,60020,60443,61532,61900,62078,63331,64623,64680,65000,65129,65389"}, "_hosts": [{"__NmapHost__": {"_starttime": "1361737906", "_endtime": "1361737906", "_hostnames": ["localhost", "localhost"], "_status": {"state": "up", "reason": "localhost-response"}, "_services": [{"__NmapService__": {"_portid": 22, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "ssh", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 25, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "smtp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 111, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "rpcbind", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 631, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "ipp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 3306, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "mysql", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}], "_extras": {"extraports": [{"state": "closed", "count": "995", "extrareasons": [{"reason": "resets", "count": "995"}]}], "times": {"srtt": "7", "rttvar": "0", "to": "100000"}}, "_extraports": [{"state": "closed", "count": "995", "extrareasons": [{"reason": "resets", "count": "995"}]}], "_osfingerprinted": false, "os": {"__NmapOSFingerprint__": {"_NmapOSFingerprint__osmatches": [], "_NmapOSFingerprint__ports_used": [], "_NmapOSFingerprint__fingerprints": []}}, "_ipv4_addr": "127.0.0.1", "_ipv6_addr": null, "_mac_addr": null, "_vendor": null, "_main_address": "127.0.0.1", "_address": [{"addr": "127.0.0.1", "addrtype": "ipv4"}]}}, {"__NmapHost__": {"_starttime": "1361737906", "_endtime": "1361738040", "_hostnames": ["scanme.nmap.org", "scanme.nmap.org"], "_status": {"state": "up", "reason": "echo-reply"}, "_services": [{"__NmapService__": {"_portid": 22, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "53"}, "_service": {"name": "ssh", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "53", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 25, "_protocol": "tcp", "_state": {"state": "filtered", "reason": "admin-prohibited", "reason_ttl": "253", "reason_ip": "109.133.192.1"}, "_service": {"name": "smtp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "admin-prohibited", "_reason_ip": "109.133.192.1", "_reason_ttl": "253", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 80, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "51"}, "_service": {"name": "http", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "51", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 9929, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "53"}, "_service": {"name": "nping-echo", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "53", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}], "_extras": {"extraports": [{"state": "closed", "count": "996", "extrareasons": [{"reason": "resets", "count": "996"}]}], "times": {"srtt": "177425", "rttvar": "1981", "to": "185349"}}, "_extraports": [{"state": "closed", "count": "996", "extrareasons": [{"reason": "resets", "count": "996"}]}], "_osfingerprinted": false, "os": {"__NmapOSFingerprint__": {"_NmapOSFingerprint__osmatches": [], "_NmapOSFingerprint__ports_used": [], "_NmapOSFingerprint__fingerprints": []}}, "_ipv4_addr": "74.207.244.221", "_ipv6_addr": null, "_mac_addr": null, "_vendor": null, "_main_address": "74.207.244.221", "_address": [{"addr": "74.207.244.221", "addrtype": "ipv4"}]}}], "_runstats": {"finished": {"time": "1361738040", "timestr": "Sun Feb 24 21:34:00 2013", "elapsed": "134.36", "summary": "Nmap done at Sun Feb 24 21:34:00 2013; 2 IP addresses (2 hosts up) scanned in 134.36 seconds", "exit": "success"}, "hosts": {"up": "2", "down": "0", "total": "2"}}}} 2 | -------------------------------------------------------------------------------- /libnmap/test/files/2_hosts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /libnmap/test/files/2_hosts_achange.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /libnmap/test/files/2_null_hosts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /libnmap/test/files/2_tcp_hosts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /libnmap/test/files/defused_et_included.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | text 4 | texttail 5 | 6 | 7 | -------------------------------------------------------------------------------- /libnmap/test/files/defused_et_local_includer.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | ]> 5 | 6 | -------------------------------------------------------------------------------- /libnmap/test/files/diff_1_host_ping_mac_changed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /libnmap/test/files/test_osclass.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | cpe:/o:linux:linux_kernel:3 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /libnmap/test/process-stressbox/check_fqp_nmap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from libnmap.parser import NmapParser, NmapParserException 3 | from libnmap.process import NmapProcess 4 | 5 | 6 | # start a new nmap scan on localhost with some specific options 7 | def do_scan(targets, options, fqp=None): 8 | parsed = None 9 | nm = NmapProcess(targets, options, fqp=fqp) 10 | rc = nm.run() 11 | if rc != 0: 12 | print("nmap scan failed: {0}".format(nm.stderr)) 13 | 14 | try: 15 | parsed = NmapParser.parse(nm.stdout) 16 | except NmapParserException as e: 17 | print("Exception raised while parsing scan: {0}".format(e.msg)) 18 | 19 | return parsed 20 | 21 | 22 | # print scan results from a nmap report 23 | def print_scan(nmap_report): 24 | print( 25 | "Starting Nmap {0} ( http://nmap.org ) at {1}".format( 26 | nmap_report.version, nmap_report.started 27 | ) 28 | ) 29 | 30 | for host in nmap_report.hosts: 31 | if len(host.hostnames): 32 | tmp_host = host.hostnames.pop() 33 | else: 34 | tmp_host = host.address 35 | 36 | print("Nmap scan report for {0} ({1})".format(tmp_host, host.address)) 37 | print("Host is {0}.".format(host.status)) 38 | print(" PORT STATE SERVICE") 39 | 40 | for serv in host.services: 41 | pserv = "{0:>5s}/{1:3s} {2:12s} {3}".format( 42 | str(serv.port), serv.protocol, serv.state, serv.service 43 | ) 44 | if len(serv.banner): 45 | pserv += " ({0})".format(serv.banner) 46 | print(pserv) 47 | print(nmap_report.summary) 48 | 49 | 50 | if __name__ == "__main__": 51 | report = do_scan("127.0.0.1", "-sT") 52 | print_scan(report) 53 | # test with full path to bin 54 | # /usr/bin/nmap 55 | report = do_scan("127.0.0.1", "-sT", fqp="/usr/bin/nmap") 56 | print_scan(report) 57 | # /usr/bin/lol --> will throw exception 58 | try: 59 | report = do_scan("127.0.0.1", "-sV", fqp="/usr/bin/lol") 60 | print("lolbin") 61 | print_scan(report) 62 | except Exception as exc: 63 | print(exc) 64 | -------------------------------------------------------------------------------- /libnmap/test/process-stressbox/multi_nmap_process.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from libnmap.process import NmapProcess 4 | 5 | 6 | def make_nmproc_obj(targets, options): 7 | return NmapProcess(targets=targets, options=options) 8 | 9 | 10 | def start_all(nmprocs): 11 | for nmp in nmprocs: 12 | print("Starting scan for host {0}".format(nmp.targets)) 13 | nmp.run() 14 | 15 | 16 | def summarize(nmprocs): 17 | for nmp in nmprocs: 18 | print("rc: {0} output: {1}".format(nmp.rc, len(nmp.stdout))) 19 | 20 | 21 | nm_targets = [] 22 | for h in range(20): 23 | nm_targets.append("localhost") 24 | nm_opts = "-sT" 25 | 26 | nm_procs = [make_nmproc_obj(t, nm_opts) for t in nm_targets] 27 | start_all(nm_procs) 28 | 29 | summarize(nm_procs) 30 | -------------------------------------------------------------------------------- /libnmap/test/process-stressbox/multi_nmap_process_background.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from time import sleep 4 | 5 | from libnmap.process import NmapProcess 6 | 7 | 8 | def make_nmproc_obj(targets, options): 9 | return NmapProcess(targets=targets, options=options) 10 | 11 | 12 | def start_all_bg(nmprocs): 13 | for nmp in nmprocs: 14 | nmp.run_background() 15 | 16 | 17 | def any_running(nmprocs): 18 | return any([nmp.is_running() for nmp in nmprocs]) 19 | 20 | 21 | def summarize(nmprocs): 22 | for nmp in nmprocs: 23 | print( 24 | "rc: {0} output: {1} stdout len: {2}".format( 25 | nmp.rc, nmp.summary, len(nmp.stdout) 26 | ) 27 | ) 28 | 29 | 30 | nm_targets = [] 31 | for h in range(10): 32 | nm_targets.append("scanme.nmap.org") 33 | nm_opts = "-sT" 34 | 35 | nm_procs = [make_nmproc_obj(t, nm_opts) for t in nm_targets] 36 | start_all_bg(nm_procs) 37 | 38 | while any_running(nm_procs): 39 | print("Nmap Scan running...") 40 | sleep(2) 41 | 42 | summarize(nm_procs) 43 | -------------------------------------------------------------------------------- /libnmap/test/process-stressbox/proc_async.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from time import sleep 4 | 5 | from libnmap.process import NmapProcess 6 | 7 | nmap_proc = NmapProcess(targets="scanme.nmap.org", options="-sT") 8 | nmap_proc.run_background() 9 | while nmap_proc.is_running(): 10 | nmaptask = nmap_proc.current_task 11 | if nmaptask: 12 | print( 13 | "Task {0} ({1}): ETC: {2} DONE: {3}%".format( 14 | nmaptask.name, nmaptask.status, nmaptask.etc, nmaptask.progress 15 | ) 16 | ) 17 | sleep(0.5) 18 | 19 | print("rc: {0} output: {1}".format(nmap_proc.rc, nmap_proc.summary)) 20 | print(nmap_proc.stdout) 21 | -------------------------------------------------------------------------------- /libnmap/test/process-stressbox/proc_nmap_like.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from libnmap.parser import NmapParser, NmapParserException 3 | from libnmap.process import NmapProcess 4 | 5 | 6 | # start a new nmap scan on localhost with some specific options 7 | def do_scan(targets, options): 8 | nm = NmapProcess(targets, options) 9 | rc = nm.run() 10 | if rc != 0: 11 | print("nmap scan failed: {0}".format(nm.stderr)) 12 | 13 | try: 14 | parsed = NmapParser.parse(nm.stdout) 15 | except NmapParserException as e: 16 | print("Exception raised while parsing scan: {0}".format(e.msg)) 17 | 18 | return parsed 19 | 20 | 21 | # print scan results from a nmap report 22 | def print_scan(nmap_report): 23 | print( 24 | "Starting Nmap {0} ( http://nmap.org ) at {1}".format( 25 | nmap_report._nmaprun["version"], nmap_report._nmaprun["startstr"] 26 | ) 27 | ) 28 | 29 | for host in nmap_report.hosts: 30 | if len(host.hostnames): 31 | tmp_host = host.hostnames.pop() 32 | else: 33 | tmp_host = host.address 34 | 35 | print("Nmap scan report for {0} ({1})".format(tmp_host, host.address)) 36 | print("Host is {0}.".format(host.status)) 37 | print(" PORT STATE SERVICE") 38 | 39 | for serv in host.services: 40 | pserv = "{0:>5s}/{1:3s} {2:12s} {3}".format( 41 | str(serv.port), serv.protocol, serv.state, serv.service 42 | ) 43 | if len(serv.banner): 44 | pserv += " ({0})".format(serv.banner) 45 | print(pserv) 46 | print(nmap_report.summary) 47 | 48 | 49 | if __name__ == "__main__": 50 | report = do_scan("127.0.0.1", "-sV") 51 | print_scan(report) 52 | -------------------------------------------------------------------------------- /libnmap/test/process-stressbox/stop_scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from time import sleep 4 | 5 | from libnmap.process import NmapProcess 6 | 7 | nmap_proc = NmapProcess(targets="scanme.nmap.org", options="-sV") 8 | nmap_proc.run_background() 9 | while nmap_proc.is_running(): 10 | nmaptask = nmap_proc.current_task 11 | if nmaptask: 12 | print( 13 | "Task {0} ({1}): ETC: {2} DONE: {3}%".format( 14 | nmaptask.name, nmaptask.status, nmaptask.etc, nmaptask.progress 15 | ) 16 | ) 17 | sleep(3) 18 | nmap_proc.stop() 19 | 20 | print("rc: {0} output: {1}".format(nmap_proc.rc, nmap_proc.summary)) 21 | print(nmap_proc.stdout) 22 | print(nmap_proc.stderr) 23 | -------------------------------------------------------------------------------- /libnmap/test/process-stressbox/stressback.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from time import sleep 4 | 5 | from libnmap.process import NmapProcess 6 | 7 | 8 | def make_nmproc_obj(targets, options): 9 | return NmapProcess(targets=targets, options=options) 10 | 11 | 12 | def start_all_bg(nmprocs): 13 | for nmp in nmprocs: 14 | nmp.run_background() 15 | 16 | 17 | def any_running(nmprocs): 18 | return any([nmp.is_running() for nmp in nmprocs]) 19 | 20 | 21 | def summarize(nmprocs): 22 | for nmp in nmprocs: 23 | print("rc: {0} output: {1}".format(nmp.rc, len(nmp.stdout))) 24 | print(nmp.stdout) 25 | 26 | 27 | nb_targets = 10 28 | nm_target = "localhost" 29 | nm_opts = "-sP" 30 | 31 | nm_targets = [nm_target for i in range(nb_targets)] 32 | nm_procs = [make_nmproc_obj(t, nm_opts) for t in nm_targets] 33 | start_all_bg(nm_procs) 34 | 35 | while any_running(nm_procs): 36 | sleep(5) 37 | 38 | summarize(nm_procs) 39 | -------------------------------------------------------------------------------- /libnmap/test/process-stressbox/stresstest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from libnmap.parser import NmapParser, NmapParserException 3 | from libnmap.process import NmapProcess 4 | 5 | nm = NmapProcess("127.0.0.1", "-sP") 6 | rc = nm.run() 7 | if rc != 0: 8 | print("nmap scan failed: {0}".format(nm.stderr)) 9 | 10 | try: 11 | report = NmapParser.parse(nm.stdout) 12 | except NmapParserException as e: 13 | print("Exception raised while parsing scan: {0}".format(e.msg)) 14 | 15 | print(len(nm.stdout)) 16 | -------------------------------------------------------------------------------- /libnmap/test/test_backend_plugin_factory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import unittest 6 | 7 | from libnmap.parser import NmapParser 8 | from libnmap.plugins.backendplugin import NmapBackendPlugin 9 | from libnmap.plugins.backendpluginFactory import BackendPluginFactory 10 | 11 | 12 | class TestNmapBackendPlugin(unittest.TestCase): 13 | """ 14 | This testing class will tests each plugins 15 | The following test need to be done : 16 | - test the factory 17 | - test all the method of the class NmapBackendPlugin: 18 | - Verify implmented/notImplemented 19 | - Verify the behaviour (ie insert must insert) 20 | To support a new plugin or a new way to instanciate a plugin, add a dict 21 | with the necessary parameter in the urls table define in setUp 22 | All testcase must loop thru theses urls to validate a plugins 23 | """ 24 | 25 | def setUp(self): 26 | fdir = os.path.dirname(os.path.realpath(__file__)) 27 | self.flist_full = [ 28 | {"file": "{0}/{1}".format(fdir, "files/2_hosts.xml"), "hosts": 2}, 29 | {"file": "{0}/{1}".format(fdir, "files/1_hosts.xml"), "hosts": 1}, 30 | { 31 | "file": "{0}/{1}".format( 32 | fdir, "files/1_hosts_banner_ports_notsyn.xml" 33 | ), 34 | "hosts": 1, 35 | }, 36 | { 37 | "file": "{0}/{1}".format( 38 | fdir, "files/1_hosts_banner_ports.xml" 39 | ), 40 | "hosts": 1, 41 | }, 42 | { 43 | "file": "{0}/{1}".format(fdir, "files/1_hosts_banner.xml"), 44 | "hosts": 1, 45 | }, 46 | { 47 | "file": "{0}/{1}".format(fdir, "files/2_hosts_version.xml"), 48 | "hosts": 2, 49 | }, 50 | { 51 | "file": "{0}/{1}".format(fdir, "files/2_tcp_hosts.xml"), 52 | "hosts": 2, 53 | }, 54 | { 55 | "file": "{0}/{1}".format(fdir, "files/1_hosts_nohostname.xml"), 56 | "hosts": 1, 57 | }, 58 | ] 59 | self.flist = self.flist_full 60 | # build a list of NmapReport 61 | self.reportList = [] 62 | for testfile in self.flist: 63 | fd = open(testfile["file"], "r") 64 | s = fd.read() 65 | fd.close() 66 | nrp = NmapParser.parse(s) 67 | self.reportList.append(nrp) 68 | 69 | self.urls = [ 70 | {"plugin_name": "mongodb"}, 71 | { 72 | "plugin_name": "sql", 73 | "url": "sqlite:////tmp/reportdb.sql", 74 | "echo": False, 75 | }, 76 | { 77 | "plugin_name": "sql", 78 | "url": "mysql+pymysql://root@localhost/poulet", 79 | "echo": False, 80 | }, 81 | ] 82 | 83 | def test_backend_factory(self): 84 | """test_factory BackendPluginFactory.create(**url) 85 | Invoke factory and test that the object is of the right classes 86 | """ 87 | for url in self.urls: 88 | backend = BackendPluginFactory.create(**url) 89 | self.assertEqual(isinstance(backend, NmapBackendPlugin), True) 90 | className = "Nmap%sPlugin" % url["plugin_name"].title() 91 | self.assertEqual(backend.__class__.__name__, className, True) 92 | 93 | def test_backend_insert(self): 94 | """test_insert 95 | best way to insert is to call save() of nmapreport :P 96 | """ 97 | for nrp in self.reportList: 98 | for url in self.urls: 99 | # create the backend factory object 100 | backend = BackendPluginFactory.create(**url) 101 | # save the report 102 | returncode = nrp.save(backend) 103 | # test return code 104 | self.assertNotEqual(returncode, None) 105 | 106 | def test_backend_get(self): 107 | """test_backend_get 108 | inset all report and save the returned id in a list 109 | then get each id and create a new list of report 110 | compare each report (assume eq) 111 | """ 112 | id_list = [] 113 | result_list = [] 114 | for url in self.urls: 115 | backend = BackendPluginFactory.create(**url) 116 | for nrp in self.reportList: 117 | id_list.append(nrp.save(backend)) 118 | for rep_id in id_list: 119 | result_list.append(backend.get(rep_id)) 120 | self.assertEqual(len(result_list), len(self.reportList)) 121 | self.assertEqual((result_list), (self.reportList)) 122 | id_list = [] 123 | result_list = [] 124 | 125 | def test_backend_getall(self): 126 | pass 127 | 128 | def test_backend_delete(self): 129 | """test_backend_delete 130 | inset all report and save the returned id in a list 131 | for each id remove the item and test if not present 132 | """ 133 | id_list = [] 134 | result_list = [] 135 | for url in self.urls: 136 | backend = BackendPluginFactory.create(**url) 137 | for nrp in self.reportList: 138 | id_list.append(nrp.save(backend)) 139 | for rep_id in id_list: 140 | result_list.append(backend.delete(rep_id)) 141 | self.assertEqual(backend.get(rep_id), None) 142 | id_list = [] 143 | result_list = [] 144 | 145 | 146 | if __name__ == "__main__": 147 | test_suite = [ 148 | "test_backend_factory", 149 | "test_backend_insert", 150 | "test_backend_get", 151 | "test_backend_getall", 152 | "test_backend_delete", 153 | ] 154 | suite = unittest.TestSuite(map(TestNmapBackendPlugin, test_suite)) 155 | test_result = unittest.TextTestRunner(verbosity=5).run(suite) 156 | -------------------------------------------------------------------------------- /libnmap/test/test_cpe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | 6 | from libnmap.objects.os import CPE 7 | 8 | 9 | class TestNmapFP(unittest.TestCase): 10 | def setUp(self): 11 | self.cpelist = [ 12 | "cpe:/a:apache:http_server:2.2.22", 13 | "cpe:/a:heimdal:kerberos", 14 | "cpe:/a:openbsd:openssh:5.9p1", 15 | "cpe:/o:apple:iphone_os:5", 16 | "cpe:/o:apple:mac_os_x:10.8", 17 | "cpe:/o:apple:mac_os_x", 18 | "cpe:/o:linux:linux_kernel:2.6.13", 19 | "cpe:/o:linux:linux_kernel", 20 | "cpe:/o:microsoft:windows_7", 21 | "cpe:/o:microsoft:windows_7::-:professional", 22 | "cpe:/o:microsoft:windows_7::sp1", 23 | "cpe:/o:microsoft:windows", 24 | "cpe:/o:microsoft:windows_server_2008::beta3", 25 | "cpe:/o:microsoft:windows_server_2008", 26 | "cpe:/o:microsoft:windows_server_2008::sp1", 27 | "cpe:/o:microsoft:windows_vista::-", 28 | "cpe:/o:microsoft:windows_vista::sp1", 29 | "cpe:/o:microsoft:windows_vista::sp2", 30 | ] 31 | 32 | def test_cpe(self): 33 | apa = CPE(self.cpelist[0]) 34 | 35 | self.assertTrue(apa.is_application()) 36 | self.assertFalse(apa.is_hardware()) 37 | self.assertFalse(apa.is_operating_system()) 38 | self.assertEqual( 39 | apa.cpedict, 40 | { 41 | "cpe": "cpe", 42 | "edition": "", 43 | "language": "", 44 | "part": "/a", 45 | "product": "http_server", 46 | "update": "", 47 | "vendor": "apache", 48 | "version": "2.2.22", 49 | }, 50 | ) 51 | 52 | win = CPE(self.cpelist[12]) 53 | self.assertEqual(win.get_vendor(), "microsoft") 54 | self.assertEqual(win.get_product(), "windows_server_2008") 55 | self.assertEqual(win.get_version(), "") 56 | self.assertEqual(win.get_update(), "beta3") 57 | self.assertEqual(win.get_edition(), "") 58 | self.assertEqual(win.get_language(), "") 59 | 60 | def test_full_cpe(self): 61 | cpestr = "cpe:/a:mozilla:firefox:2.0::osx:es-es" 62 | resdict = { 63 | "part": "/a", 64 | "vendor": "mozilla", 65 | "product": "firefox", 66 | "version": "2.0", 67 | "update": "", 68 | "edition": "osx", 69 | "language": "es-es", 70 | } 71 | ocpe = CPE(cpestr) 72 | objdict = { 73 | "part": ocpe.get_part(), 74 | "vendor": ocpe.get_vendor(), 75 | "product": ocpe.get_product(), 76 | "version": ocpe.get_version(), 77 | "update": ocpe.get_update(), 78 | "language": ocpe.get_language(), 79 | "edition": ocpe.get_edition(), 80 | } 81 | self.assertEqual(objdict, resdict) 82 | # self.assertEqual(ocpe.cpedict, resdict) 83 | self.assertEqual(str(ocpe), cpestr) 84 | 85 | 86 | if __name__ == "__main__": 87 | test_suite = ["test_cpe", "test_full_cpe"] 88 | suite = unittest.TestSuite(map(TestNmapFP, test_suite)) 89 | test_result = unittest.TextTestRunner(verbosity=2).run(suite) 90 | -------------------------------------------------------------------------------- /libnmap/test/test_defusedxml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import unittest 7 | 8 | from libnmap.parser import NmapParser, NmapParserException 9 | 10 | 11 | class TestDefusedXML(unittest.TestCase): 12 | def setUp(self): 13 | if int(sys.version[0]) == 3: 14 | self._assertRaisesRegex = self.assertRaisesRegex 15 | else: 16 | self._assertRaisesRegex = self.assertRaisesRegexp 17 | 18 | self.billionlaugh = """ 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ]> 32 | &lol9; 33 | """ 34 | self.fdir = os.path.dirname(os.path.realpath(__file__)) 35 | self.billionlaugh_file = "{0}/files/{1}".format( 36 | self.fdir, "billion_laugh.xml" 37 | ) 38 | self.external_entities_file = "{0}/files/{1}".format( 39 | self.fdir, "defused_et_local_includer.xml" 40 | ) 41 | 42 | def test_billion_laugh(self): 43 | self._assertRaisesRegex( 44 | NmapParserException, 45 | ".*EntitiesForbidden", 46 | NmapParser.parse_fromstring, 47 | self.billionlaugh, 48 | ) 49 | 50 | def test_external_entities(self): 51 | self._assertRaisesRegex( 52 | NmapParserException, 53 | ".*EntitiesForbidden", 54 | NmapParser.parse_fromfile, 55 | self.external_entities_file, 56 | ) 57 | 58 | 59 | if __name__ == "__main__": 60 | # test_suite = ["test_external_entities"] 61 | test_suite = ["test_billion_laugh", "test_external_entities"] 62 | suite = unittest.TestSuite(map(TestDefusedXML, test_suite)) 63 | test_result = unittest.TextTestRunner(verbosity=2).run(suite) 64 | -------------------------------------------------------------------------------- /libnmap/test/test_extraports.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import unittest 6 | 7 | from libnmap.parser import NmapParser, NmapParserException 8 | 9 | 10 | class TestExtraPorts(unittest.TestCase): 11 | def setUp(self): 12 | fdir = os.path.dirname(os.path.realpath(__file__)) 13 | _extrareasons = [ 14 | {"reason": "filtered", "count": "3"}, 15 | {"reason": "resets", "count": "7"}, 16 | ] 17 | self.flist = [ 18 | { 19 | "path": "%s/%s" % (fdir, "files/extra_ports.xml"), 20 | "extrareasons": _extrareasons, 21 | } 22 | ] 23 | 24 | def test_extraports(self): 25 | for fentry in self.flist: 26 | rep1 = NmapParser.parse_fromfile(fentry["path"]) 27 | ep_list = rep1.hosts[0].extraports 28 | self.assertEqual(len(ep_list), 2) 29 | self.assertEqual(ep_list[0]["count"], "65509") 30 | self.assertEqual(ep_list[0]["state"], "closed") 31 | self.assertEqual(len(ep_list[0]["extrareasons"]), 1) 32 | self.assertEqual(ep_list[1]["count"], "10") 33 | self.assertEqual(len(ep_list[1]["extrareasons"]), 2) 34 | self.assertEqual( 35 | ep_list[1]["extrareasons"], fentry["extrareasons"] 36 | ) 37 | 38 | 39 | if __name__ == "__main__": 40 | test_suite = [ 41 | "test_extraports", 42 | ] 43 | suite = unittest.TestSuite(map(TestExtraPorts, test_suite)) 44 | test_result = unittest.TextTestRunner(verbosity=2).run(suite) 45 | -------------------------------------------------------------------------------- /libnmap/test/test_new_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | 6 | from libnmap.parser import NmapParser, NmapParserException 7 | 8 | baddatalist = [ 9 | "aaa", 10 | None, 11 | "", 12 | 123, 13 | "ports/>>>", 14 | "", 15 | "", 16 | "", 17 | ] 18 | 19 | 20 | class TestNmapParser(unittest.TestCase): 21 | def test_parse(self): 22 | for baddata in baddatalist: 23 | self.assertRaises( 24 | NmapParserException, NmapParser.parse, baddata, "zz" 25 | ) 26 | self.assertRaises( 27 | NmapParserException, NmapParser.parse, baddata, "XML" 28 | ) 29 | self.assertRaises( 30 | NmapParserException, NmapParser.parse, baddata, "YAML" 31 | ) 32 | 33 | 34 | if __name__ == "__main__": 35 | test_suite = ["test_parse"] 36 | 37 | suite = unittest.TestSuite(map(TestNmapParser, test_suite)) 38 | test_result = unittest.TextTestRunner(verbosity=2).run(suite) 39 | -------------------------------------------------------------------------------- /libnmap/test/test_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import unittest 6 | 7 | from libnmap.parser import NmapParser, NmapParserException 8 | 9 | 10 | class TestNmapParser(unittest.TestCase): 11 | def setUp(self): 12 | fdir = os.path.dirname(os.path.realpath(__file__)) 13 | self.flist_full = [ 14 | {"file": "%s/%s" % (fdir, "files/2_hosts.xml"), "hosts": 2}, 15 | {"file": "%s/%s" % (fdir, "files/1_hosts.xml"), "hosts": 1}, 16 | { 17 | "file": "%s/%s" 18 | % (fdir, "files/1_hosts_banner_ports_notsyn.xml"), 19 | "hosts": 1, 20 | }, 21 | # {'file': "%s/%s" % (fdir, 22 | # 'files/1_hosts_banner_ports_xmas.xml'), 23 | # 'hosts': 1}, 24 | { 25 | "file": "%s/%s" % (fdir, "files/1_hosts_banner_ports.xml"), 26 | "hosts": 1, 27 | }, 28 | {"file": "%s/%s" % (fdir, "files/1_hosts_banner.xml"), "hosts": 1}, 29 | { 30 | "file": "%s/%s" % (fdir, "files/2_hosts_version.xml"), 31 | "hosts": 2, 32 | }, 33 | # {'file': "%s/%s" % (fdir, 34 | # 'files/2_null_hosts.xml'), 'hosts': 2}, 35 | {"file": "%s/%s" % (fdir, "files/2_tcp_hosts.xml"), "hosts": 2}, 36 | { 37 | "file": "%s/%s" % (fdir, "files/1_hosts_nohostname.xml"), 38 | "hosts": 1, 39 | }, 40 | ] 41 | self.flist = self.flist_full 42 | 43 | self.ports_string = """ 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | """ 64 | 65 | self.ports_string2 = """ 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 75 | 76 | 77 | 78 | i 79 | 80 | 81 | 82 | 83 | 84 | """ 85 | 86 | self.port_string = """ 87 | 88 | 90 | 91 | """ 92 | 93 | self.port_string2 = """ 94 | 95 | 97 | 98 | """ 99 | 100 | self.port_string3 = "" 101 | self.port_string4 = "" 102 | self.port_string5 = "GINGERBREADMAN" 103 | self.port_string6 = """ 104 | 105 | 107 | 108 | """ 109 | 110 | self.port_string7 = """ 111 | 112 | 114 | """ 115 | 116 | self.port_string8 = """ 117 | 118 | 119 | """ 120 | self.port_string9 = """ 121 | 122 | 123 | 124 | """ 125 | 126 | def test_class_parser(self): 127 | for testfile in self.flist: 128 | fd = open(testfile["file"], "r") 129 | s = fd.read() 130 | fd.close() 131 | NmapParser.parse(s) 132 | 133 | def test_class_ports_parser(self): 134 | pdict = NmapParser.parse(self.ports_string) 135 | plist = pdict["ports"] 136 | self.assertEqual(len(plist), 4) 137 | self.assertEqual( 138 | sorted([p.port for p in plist]), sorted([22, 25, 9929, 80]) 139 | ) 140 | self.assertRaises(ValueError, NmapParser.parse, self.ports_string2) 141 | 142 | def test_class_port_parser(self): 143 | p = NmapParser.parse(self.port_string) 144 | self.assertEqual(p.port, 25) 145 | self.assertNotEqual(p.state, "open") 146 | self.assertEqual(p.state, "filtered") 147 | self.assertEqual(p.service, "smtp") 148 | self.assertEqual(p.reason, "admin-prohibited") 149 | self.assertEqual(p.reason_ttl, "253") 150 | self.assertEqual(p.reason_ip, "109.133.192.1") 151 | 152 | def test_port_except(self): 153 | self.assertRaises(ValueError, NmapParser.parse, self.port_string2) 154 | self.assertRaises( 155 | NmapParserException, NmapParser.parse, self.port_string3 156 | ) 157 | self.assertRaises( 158 | NmapParserException, NmapParser.parse, self.port_string4 159 | ) 160 | self.assertRaises( 161 | NmapParserException, NmapParser.parse, self.port_string5 162 | ) 163 | self.assertRaises(ValueError, NmapParser.parse, self.port_string6) 164 | self.assertRaises( 165 | NmapParserException, NmapParser.parse, self.port_string7 166 | ) 167 | self.assertRaises( 168 | NmapParserException, NmapParser.parse, self.port_string8 169 | ) 170 | serv = NmapParser.parse(self.port_string9) 171 | self.assertEqual(serv.state, None) 172 | 173 | def test_parser_generic(self): 174 | plist = NmapParser.parse_fromstring(self.ports_string) 175 | for p in plist: 176 | print(p) 177 | 178 | 179 | if __name__ == "__main__": 180 | test_suite = [ 181 | "test_class_parser", 182 | "test_class_ports_parser", 183 | "test_class_port_parser", 184 | "test_port_except", 185 | "test_parser_generic", 186 | ] 187 | suite = unittest.TestSuite(map(TestNmapParser, test_suite)) 188 | test_result = unittest.TextTestRunner(verbosity=2).run(suite) 189 | -------------------------------------------------------------------------------- /libnmap/test/test_process.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import unittest 7 | from time import sleep 8 | 9 | from libnmap.objects.report import NmapReport 10 | from libnmap.parser import NmapParser 11 | from libnmap.process import NmapProcess 12 | 13 | 14 | class TestNmapProcess(unittest.TestCase): 15 | def setUp(self): 16 | if int(sys.version[0]) == 3: 17 | self._assertRaisesRegex = self.assertRaisesRegex 18 | else: 19 | self._assertRaisesRegex = self.assertRaisesRegexp 20 | self.fdir = os.path.dirname(os.path.realpath(__file__)) 21 | 22 | def test_check_valid_targets(self): 23 | valid_target_tests = [ 24 | {"value": "127.0.0.1, 1.1.1.1, 2.20.202", "size": 3}, 25 | {"value": ["127.0.0.1", "1.1.1.1", "2.20.202.2"], "size": 3}, 26 | {"value": [" 127.0.0.1", " 1.1.1.1"], "size": 2}, 27 | {"value": " 127.0.0.1, 1.1.1.1 , a", "size": 3}, 28 | {"value": ["192.168.10.0/24", "192.168.0-255.1-254"], "size": 2}, 29 | {"value": ["fe80::a8bb:ccff:fedd:eeff%eth0"], "size": 1}, 30 | {"value": ["my-domain.com", "my-num3r1c-domain.com"], "size": 2}, 31 | ] 32 | for vtarget in valid_target_tests: 33 | nmapobj = NmapProcess(targets=vtarget["value"], options="-sP") 34 | self.assertEqual(vtarget["size"], len(nmapobj.targets)) 35 | 36 | def test_check_invalid_targets(self): 37 | invalid_target_type_tests = [{"a": "bba"}, 5] 38 | invalid_target_character_tests = ["1.1.1.1$", "invalid_domain.com"] 39 | invalid_target_dash_tests = ["-invalid-target", "--option"] 40 | 41 | for vtarget in invalid_target_type_tests: 42 | self._assertRaisesRegex( 43 | Exception, 44 | "Supplied target list should be either a string or a list", 45 | NmapProcess, 46 | targets=vtarget, 47 | options="-sP", 48 | ) 49 | 50 | for vtarget in invalid_target_character_tests: 51 | self._assertRaisesRegex( 52 | Exception, 53 | "contains invalid characters", 54 | NmapProcess, 55 | targets=vtarget, 56 | options="-sP", 57 | ) 58 | 59 | for vtarget in invalid_target_dash_tests: 60 | self._assertRaisesRegex( 61 | Exception, 62 | "cannot begin or end with a dash", 63 | NmapProcess, 64 | targets=vtarget, 65 | options="-sP", 66 | ) 67 | 68 | def test_nmap_options(self): 69 | invalid_options = ["--iflist"] 70 | 71 | for invalid_option in invalid_options: 72 | self._assertRaisesRegex( 73 | Exception, 74 | "unsafe options activated while safe_mode is set True", 75 | NmapProcess, 76 | targets="127.0.0.1", 77 | options=invalid_option, 78 | ) 79 | 80 | def test_missing_binary(self): 81 | _path = os.environ["PATH"] 82 | os.environ["PATH"] = "/does_not_exists" 83 | self._assertRaisesRegex( 84 | EnvironmentError, 85 | "nmap is not installed or could not be found in system path", 86 | NmapProcess, 87 | targets="127.0.0.1", 88 | options="-sP", 89 | ) 90 | os.environ["PATH"] = _path 91 | 92 | def test_exec_env(self): 93 | self.assertRaises( 94 | EnvironmentError, 95 | NmapProcess, 96 | targets="127.0.0.1", 97 | options="-sV", 98 | fqp="/usr/bin/does-not-exists", 99 | ) 100 | 101 | def test_exec(self): 102 | nmapobj = NmapProcess(targets="127.0.0.1", options="-sP") 103 | rc = nmapobj.run() 104 | parsed = NmapParser.parse(nmapobj.stdout) 105 | self.assertEqual(rc, 0) 106 | self.assertGreater(len(nmapobj.stdout), 0) 107 | self.assertIsInstance(parsed, NmapReport) 108 | 109 | def test_sudo_exec(self): 110 | nmapobj = NmapProcess(targets="127.0.0.1", options="-sP") 111 | self._assertRaisesRegex( 112 | EnvironmentError, 113 | "Username.*does not exists", 114 | nmapobj.sudo_run, 115 | run_as="non-existing-user", 116 | ) 117 | self._assertRaisesRegex( 118 | EnvironmentError, 119 | "Username.*does not exists", 120 | nmapobj.sudo_run_background, 121 | run_as="non-existing-user", 122 | ) 123 | 124 | def test_exec_reportsize(self): 125 | def make_nmproc_obj(targets, options): 126 | return NmapProcess(targets=targets, options=options) 127 | 128 | def start_all(nmprocs): 129 | for nmp in nmprocs: 130 | nmp.run() 131 | 132 | nb_targets = 20 133 | nm_target = "localhost" 134 | nm_opts = "-sT" 135 | 136 | nm_targets = [nm_target for i in range(nb_targets)] 137 | nm_procs = [make_nmproc_obj(t, nm_opts) for t in nm_targets] 138 | start_all(nm_procs) 139 | 140 | nm_procs = [make_nmproc_obj(t, nm_opts) for t in nm_targets] 141 | start_all(nm_procs) 142 | 143 | self.assertEqual(len(nm_procs), nb_targets) 144 | 145 | total_size = 0 146 | for i in range(len(nm_procs)): 147 | total_size += len(nm_procs[i].stdout) 148 | 149 | average_size = int(total_size / len(nm_procs)) 150 | for nm in nm_procs: 151 | self.assertAlmostEqual( 152 | average_size, int(len(nm.stdout)), delta=200 153 | ) 154 | 155 | 156 | if __name__ == "__main__": 157 | test_suite = [ 158 | "test_exec_env", 159 | "test_check_targets", 160 | "test_exec", 161 | "test_exec_reportsize", 162 | ] 163 | suite = unittest.TestSuite(map(TestNmapProcess, test_suite)) 164 | test_result = unittest.TextTestRunner(verbosity=2).run(suite) 165 | -------------------------------------------------------------------------------- /libnmap/test/test_report_diff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import unittest 6 | 7 | from libnmap.parser import NmapParser 8 | 9 | 10 | class TestNmapReportDiff(unittest.TestCase): 11 | def setUp(self): 12 | fdir = os.path.dirname(os.path.realpath(__file__)) 13 | self.flist_full = [ 14 | {"file": "%s/%s" % (fdir, "files/2_hosts.xml"), "hosts": 2}, 15 | {"file": "%s/%s" % (fdir, "files/1_hosts.xml"), "hosts": 1}, 16 | ] 17 | self.flist = self.flist_full 18 | 19 | def test_diff_host_list(self): 20 | fdir = os.path.dirname(os.path.realpath(__file__)) 21 | r1 = NmapParser.parse_fromfile("%s/%s" % (fdir, "files/1_hosts.xml")) 22 | r2 = NmapParser.parse_fromfile("%s/%s" % (fdir, "files/2_hosts.xml")) 23 | r3 = NmapParser.parse_fromfile("%s/%s" % (fdir, "files/1_hosts.xml")) 24 | r4 = NmapParser.parse_fromfile( 25 | "%s/%s" % (fdir, "files/2_hosts_achange.xml") 26 | ) 27 | 28 | d1 = r1.diff(r2) 29 | self.assertEqual( 30 | d1.changed(), 31 | set( 32 | [ 33 | "hosts_total", 34 | "commandline", 35 | "hosts_up", 36 | "scan_type", 37 | "elapsed", 38 | ] 39 | ), 40 | ) 41 | self.assertEqual( 42 | d1.unchanged(), 43 | set(["hosts_down", "version", "NmapHost::127.0.0.1"]), 44 | ) 45 | self.assertEqual(d1.removed(), set(["NmapHost::74.207.244.221"])) 46 | 47 | d2 = r1.diff(r3) 48 | self.assertEqual(d2.changed(), set([])) 49 | self.assertEqual( 50 | d2.unchanged(), 51 | set( 52 | [ 53 | "hosts_total", 54 | "commandline", 55 | "hosts_up", 56 | "NmapHost::127.0.0.1", 57 | "elapsed", 58 | "version", 59 | "scan_type", 60 | "hosts_down", 61 | ] 62 | ), 63 | ) 64 | self.assertEqual(d2.added(), set([])) 65 | self.assertEqual(d2.removed(), set([])) 66 | 67 | d3 = r2.diff(r4) 68 | self.assertEqual(d3.changed(), set(["NmapHost::127.0.0.1"])) 69 | self.assertEqual( 70 | d3.unchanged(), 71 | set( 72 | [ 73 | "hosts_total", 74 | "commandline", 75 | "hosts_up", 76 | "NmapHost::74.207.244.221", 77 | "version", 78 | "elapsed", 79 | "scan_type", 80 | "hosts_down", 81 | ] 82 | ), 83 | ) 84 | self.assertEqual(d3.added(), set([])) 85 | self.assertEqual(d3.removed(), set([])) 86 | 87 | 88 | if __name__ == "__main__": 89 | test_suite = ["test_diff_host_list"] 90 | suite = unittest.TestSuite(map(TestNmapReportDiff, test_suite)) 91 | test_result = unittest.TextTestRunner(verbosity=2).run(suite) 92 | -------------------------------------------------------------------------------- /libnmap/test/test_reportjson.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | import os 6 | import unittest 7 | 8 | from libnmap.parser import NmapParser 9 | from libnmap.reportjson import ReportDecoder, ReportEncoder 10 | 11 | 12 | class TestReportJson(unittest.TestCase): 13 | def setUp(self): 14 | self.fdir = os.path.dirname(os.path.realpath(__file__)) 15 | self.xml_ref_file = "{0}/files/2_hosts.xml".format(self.fdir) 16 | self.json_ref_file = "{0}/files/2_hosts.json".format(self.fdir) 17 | 18 | def test_reportencode(self): 19 | nmap_report_obj = NmapParser.parse_fromfile(self.xml_ref_file) 20 | nmap_report_json = json.loads( 21 | json.dumps(nmap_report_obj, cls=ReportEncoder) 22 | ) 23 | with open(self.json_ref_file, "r") as fd: 24 | nmap_report_json_ref = json.load(fd) 25 | self.assertEqual(nmap_report_json_ref, nmap_report_json) 26 | 27 | def test_reportdecode(self): 28 | nmap_report_obj_ref = NmapParser.parse_fromfile(self.xml_ref_file) 29 | 30 | with open(self.json_ref_file, "r") as fd: 31 | nmap_report_json_ref = json.dumps(json.load(fd)) 32 | nmap_report_obj = json.loads( 33 | nmap_report_json_ref, cls=ReportDecoder 34 | ) 35 | self.assertEqual(nmap_report_obj_ref, nmap_report_obj) 36 | 37 | 38 | if __name__ == "__main__": 39 | test_suite = ["test_reportencode", "test_reportdecode"] 40 | suite = unittest.TestSuite(map(TestReportJson, test_suite)) 41 | test_result = unittest.TextTestRunner(verbosity=2).run(suite) 42 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | black==24.3.0 2 | defusedxml==0.7.1 3 | isort==6.0.0 4 | pre-commit 5 | pytest 6 | pytest-cov 7 | flake8 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | 4 | try: 5 | from setuptools import setup 6 | except ImportError: 7 | from distutils.core import setup 8 | 9 | if sys.version_info >= (3, 0): 10 | with open("README.rst", encoding="utf-8") as rfile: 11 | long_description = rfile.read() 12 | else: # if encoding not compatible with python2 13 | with open("README.rst") as rfile: 14 | long_description = rfile.read() 15 | 16 | setup( 17 | name="python-libnmap", 18 | version="0.7.3", 19 | author="Ronald Bister", 20 | author_email="mini.pelle@gmail.com", 21 | packages=["libnmap", "libnmap.plugins", "libnmap.objects"], 22 | url="http://pypi.python.org/pypi/python-libnmap/", 23 | extras_require={ 24 | "defusedxml": ["defusedxml>=0.6.0"], 25 | }, 26 | license="Apache 2.0", 27 | description=( 28 | "Python NMAP library enabling you to start async nmap tasks, " 29 | "parse and compare/diff scan results" 30 | ), 31 | long_description=long_description, 32 | classifiers=[ 33 | "License :: OSI Approved :: Apache Software License", 34 | "Development Status :: 5 - Production/Stable", 35 | "Environment :: Console", 36 | "Programming Language :: Python :: 2", 37 | "Programming Language :: Python :: 2.7", 38 | "Programming Language :: Python :: 3", 39 | "Programming Language :: Python :: 3.3", 40 | "Programming Language :: Python :: 3.4", 41 | "Programming Language :: Python :: 3.5", 42 | "Programming Language :: Python :: 3.6", 43 | "Programming Language :: Python :: 3.7", 44 | "Programming Language :: Python :: 3.8", 45 | "Programming Language :: Python :: 3.9", 46 | "Programming Language :: Python :: 3.10", 47 | "Topic :: System :: Networking", 48 | ], 49 | ) 50 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27, py32, py38, flake8, pycodestyle, formatting, defusedxml, coveralls 3 | 4 | [testenv] 5 | deps=pytest 6 | pytest-cov 7 | commands=pytest --cov --cov-report term-missing --ignore=libnmap/test/test_backend_plugin_factory.py --ignore=libnmap/test/test_defusedxml.py 8 | 9 | [testenv:defusedxml] 10 | deps=pytest 11 | defusedxml 12 | commands=pytest --ignore=libnmap/test/test_backend_plugin_factory.py 13 | 14 | [testenv:dbbackend] 15 | deps=pytest 16 | pymongo 17 | sqlalchemy 18 | pymysql 19 | commands=pytest --ignore=libnmap/test/test_defusedxml.py 20 | 21 | [testenv:flake8] 22 | deps = 23 | flake8 24 | commands = 25 | flake8 --exclude test,docs,examples,.tox . 26 | 27 | [testenv:pycodestyle] 28 | deps = 29 | pycodestyle 30 | commands = 31 | pycodestyle --exclude test,docs,examples,.tox . 32 | 33 | [testenv:formatting] 34 | deps = 35 | #black==20.8b1 36 | black 37 | isort 38 | commands = 39 | black --check -l 79 --exclude="venv|.tox" . 40 | isort --check-only -m 3 -l 79 --profile=black . 41 | --------------------------------------------------------------------------------