├── pydvnr ├── __init__.py ├── __main__.py └── pydvnr_utils.py ├── requirements.txt ├── README.md ├── setup.py └── .gitignore /pydvnr/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | psutil==5.4.5 -------------------------------------------------------------------------------- /pydvnr/__main__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | logger = logging.getLogger('pydvnr') 5 | logger.addHandler(logging.FileHandler('contrast-pydvr.txt')) 6 | logger.setLevel(logging.INFO) 7 | 8 | from . import pydvnr_utils 9 | 10 | 11 | def main(): 12 | if '-s' in sys.argv: 13 | logger.handlers = [] 14 | 15 | logger.addHandler(logging.StreamHandler(sys.stdout)) 16 | else: 17 | print('Output is shown in contrast-pydvr.txt') 18 | 19 | pydvnr_utils.show_banner() 20 | 21 | pydvnr_utils.get_basic_information() 22 | 23 | pydvnr_utils.get_python_version() 24 | 25 | pydvnr_utils.check_pip_version() 26 | 27 | pydvnr_utils.get_framework() 28 | 29 | 30 | if __name__ == '__main__': 31 | main() 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Contrast Python Diviner 2 | 3 | ## Description 4 | 5 | The Contrast Python Diviner is a utility application to determine compatibility with the Contrast Python Agent 6 | 7 | ### Information Displayed 8 | 9 | * Operating System 10 | * Memory and CPU 11 | * Pip 12 | * Installed packages 13 | * Python version 14 | * Frameworks 15 | 16 | ### Update requirements.txt: 17 | 18 | Via Github: 19 | 20 | ```bash 21 | git clone https://github.com/Contrast-Security-OSS/contrast-pydvnr.git 22 | cd contrast-pydvnr 23 | pip install . 24 | 25 | ``` 26 | 27 | ### To run locally: 28 | 29 | ```bash 30 | contrast-pydvnr 31 | ``` 32 | 33 | ### Information: 34 | 35 | Outputted in `contrast-pydvnr.txt` 36 | 37 | ### Configuration: 38 | 39 | `-s` can be used to force standard out -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from io import open 2 | from os import path 3 | 4 | from setuptools import setup 5 | 6 | root_dir = path.abspath(path.dirname(__file__)) 7 | 8 | # Get the long description from the README file 9 | with open(path.join(root_dir, 'README.md'), encoding='utf-8') as f: 10 | long_description = f.read() 11 | 12 | 13 | setup( 14 | name='contrast-pydvnr', 15 | 16 | version='0.0.1', 17 | 18 | description='Contrast Python Diviner', 19 | long_description=long_description, 20 | 21 | # The project's main homepage. 22 | url='', 23 | 24 | # Author details 25 | author='Contrast Security, Inc.', 26 | author_email='justin.leo@contrastsecurity.com', 27 | 28 | # Choose your license 29 | license='MIT', 30 | 31 | classifiers=[ 32 | # Audience 33 | 'Intended Audience :: Developers', 34 | 35 | # supported languages 36 | 'Programming Language :: Python :: 2', 37 | 'Programming Language :: Python :: 2.7', 38 | 'Programming Language :: Python :: 3', 39 | 'Programming Language :: Python :: 3.4', 40 | 'Programming Language :: Python :: 3.5', 41 | 'Programming Language :: Python :: 3.6', 42 | ], 43 | 44 | keywords='security development', 45 | 46 | packages=['pydvnr'], 47 | package_dir={'pydvnr': 'pydvnr'}, 48 | include_package_data=True, 49 | 50 | requires=['psutil'], 51 | 52 | install_requires=[], 53 | 54 | extras_require={}, 55 | 56 | package_data={}, 57 | 58 | data_files=[], 59 | entry_points={ 60 | 'console_scripts': [ 61 | 'contrast-pydvnr = pydvnr.__main__:main' 62 | ] 63 | }, 64 | ) 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # Sensitive or high-churn files 13 | .idea/**/dataSources/ 14 | .idea/**/dataSources.ids 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | 20 | # Gradle 21 | .idea/**/gradle.xml 22 | .idea/**/libraries 23 | 24 | # CMake 25 | cmake-build-debug/ 26 | cmake-build-release/ 27 | 28 | # Mongo Explorer plugin 29 | .idea/**/mongoSettings.xml 30 | 31 | # File-based project format 32 | *.iws 33 | 34 | # IntelliJ 35 | out/ 36 | 37 | # mpeltonen/sbt-idea plugin 38 | .idea_modules/ 39 | 40 | # JIRA plugin 41 | atlassian-ide-plugin.xml 42 | 43 | # Cursive Clojure plugin 44 | .idea/replstate.xml 45 | 46 | # Crashlytics plugin (for Android Studio and IntelliJ) 47 | com_crashlytics_export_strings.xml 48 | crashlytics.properties 49 | crashlytics-build.properties 50 | fabric.properties 51 | 52 | # Editor-based Rest Client 53 | .idea/httpRequests 54 | ### VirtualEnv template 55 | # Virtualenv 56 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 57 | .Python 58 | [Bb]in 59 | [Ii]nclude 60 | [Ll]ib 61 | [Ll]ib64 62 | [Ll]ocal 63 | [Ss]cripts 64 | pyvenv.cfg 65 | .venv 66 | pip-selfcheck.json 67 | ### Linux template 68 | *~ 69 | 70 | # temporary files which can be created if a process still has a handle open of a deleted file 71 | .fuse_hidden* 72 | 73 | # KDE directory preferences 74 | .directory 75 | 76 | # Linux trash folder which might appear on any partition or disk 77 | .Trash-* 78 | 79 | # .nfs files are created when an open file is removed but is still being accessed 80 | .nfs* 81 | ### Python template 82 | # Byte-compiled / optimized / DLL files 83 | __pycache__/ 84 | *.py[cod] 85 | *$py.class 86 | 87 | # C extensions 88 | *.so 89 | 90 | # Distribution / packaging 91 | .Python 92 | build/ 93 | develop-eggs/ 94 | dist/ 95 | downloads/ 96 | eggs/ 97 | .eggs/ 98 | lib/ 99 | lib64/ 100 | parts/ 101 | sdist/ 102 | var/ 103 | wheels/ 104 | *.egg-info/ 105 | .installed.cfg 106 | *.egg 107 | MANIFEST 108 | 109 | # PyInstaller 110 | # Usually these files are written by a python script from a template 111 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 112 | *.manifest 113 | *.spec 114 | 115 | # Installer logs 116 | pip-log.txt 117 | pip-delete-this-directory.txt 118 | 119 | # Unit test / coverage reports 120 | htmlcov/ 121 | .tox/ 122 | .coverage 123 | .coverage.* 124 | .cache 125 | nosetests.xml 126 | coverage.xml 127 | *.cover 128 | .hypothesis/ 129 | .pytest_cache/ 130 | 131 | # Translations 132 | *.mo 133 | *.pot 134 | 135 | # Django stuff: 136 | *.log 137 | local_settings.py 138 | db.sqlite3 139 | 140 | # Flask stuff: 141 | instance/ 142 | .webassets-cache 143 | 144 | # Scrapy stuff: 145 | .scrapy 146 | 147 | # Sphinx documentation 148 | docs/_build/ 149 | 150 | # PyBuilder 151 | target/ 152 | 153 | # Jupyter Notebook 154 | .ipynb_checkpoints 155 | 156 | # pyenv 157 | .python-version 158 | 159 | # celery beat schedule file 160 | celerybeat-schedule 161 | 162 | # SageMath parsed files 163 | *.sage.py 164 | 165 | # Environments 166 | .env 167 | .venv 168 | env/ 169 | venv/ 170 | ENV/ 171 | env.bak/ 172 | venv.bak/ 173 | 174 | # Spyder project settings 175 | .spyderproject 176 | .spyproject 177 | 178 | # Rope project settings 179 | .ropeproject 180 | 181 | # mkdocs documentation 182 | /site 183 | 184 | # mypy 185 | .mypy_cache/ 186 | ### macOS template 187 | # General 188 | .DS_Store 189 | .AppleDouble 190 | .LSOverride 191 | 192 | # Icon must end with two \r 193 | Icon 194 | 195 | # Thumbnails 196 | ._* 197 | 198 | # Files that might appear in the root of a volume 199 | .DocumentRevisions-V100 200 | .fseventsd 201 | .Spotlight-V100 202 | .TemporaryItems 203 | .Trashes 204 | .VolumeIcon.icns 205 | .com.apple.timemachine.donotpresent 206 | 207 | # Directories potentially created on remote AFP share 208 | .AppleDB 209 | .AppleDesktop 210 | Network Trash Folder 211 | Temporary Items 212 | .apdisk 213 | ### Windows template 214 | # Windows thumbnail cache files 215 | Thumbs.db 216 | ehthumbs.db 217 | ehthumbs_vista.db 218 | 219 | # Dump file 220 | *.stackdump 221 | 222 | # Folder config file 223 | [Dd]esktop.ini 224 | 225 | # Recycle Bin used on file shares 226 | $RECYCLE.BIN/ 227 | 228 | # Windows Installer files 229 | *.cab 230 | *.msi 231 | *.msix 232 | *.msm 233 | *.msp 234 | 235 | # Windows shortcuts 236 | *.lnk 237 | 238 | -------------------------------------------------------------------------------- /pydvnr/pydvnr_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import platform 4 | import sys 5 | 6 | import psutil 7 | 8 | logger = logging.getLogger('pydvnr') 9 | 10 | 11 | def show_banner(): 12 | banner = """ 13 | ____ __ 14 | / __ \__ ______/ / ______ _____ 15 | / /_/ / / / / __ / | / / __ \/ ___/ 16 | / ____/ /_/ / /_/ /| |/ / / / / / 17 | /_/ \__, /\__,_/ |___/_/ /_/_/ 18 | /____/ 19 | 20 | """ 21 | 22 | logger.info(banner) 23 | 24 | 25 | def get_basic_information(): 26 | logger.info('---------- Operating System ----------') 27 | 28 | os_version = platform.platform() 29 | release = platform.release() 30 | version = platform.version() 31 | 32 | logger.info('OS: %s', os_version) 33 | logger.info('Release: %s', release) 34 | logger.info('Version: %s', version) 35 | logger.info('CWD: %s', os.getcwd()) 36 | 37 | logger.info('---------- Memory ----------') 38 | 39 | memory = psutil.virtual_memory() 40 | total_memory = memory.total >> 30 41 | available_memory = memory.available >> 30 42 | percent_memory = memory.percent 43 | 44 | logger.info('%s GB of %s GB available - %s', available_memory, total_memory, percent_memory) 45 | 46 | logger.info('---------- CPU ----------') 47 | processors = psutil.cpu_count() 48 | logger.info('%s processors', processors) 49 | 50 | 51 | def get_python_version(): 52 | logger.info('---------- Python Version ----------') 53 | full_python_version = sys.version.split('\n')[0] 54 | 55 | python_version = sys.version_info[:3] 56 | 57 | joined_version = '.'.join(map(str, python_version)) 58 | 59 | logger.info(full_python_version) 60 | 61 | check_python_version(python_version, joined_version) 62 | 63 | logger.info('---------- Python Path ----------') 64 | 65 | logger.info(sys.executable) 66 | 67 | 68 | def get_package_information(pip, pip_version): 69 | logger.info('---------- Packages ----------') 70 | 71 | packages = [] 72 | 73 | if not pip_version.startswith('10'): 74 | packages = pip.get_installed_distributions() 75 | elif pip_version.startswith('10'): 76 | from pip._internal.utils.misc import get_installed_distributions 77 | 78 | packages = get_installed_distributions() 79 | 80 | packages.sort(key=lambda x: x.key) 81 | 82 | for package in packages: 83 | logger.info(repr(package)) 84 | 85 | for dep, reqs in list(package._dep_map.items()): 86 | for req in reqs: 87 | logger.info('\t' + str(req)) 88 | 89 | 90 | def get_framework(): 91 | # django, flask, pyramid, etc 92 | logger.info('---------- Framework ----------') 93 | 94 | try: 95 | import django 96 | 97 | logger.info('Django %s', django.get_version()) 98 | except: 99 | pass 100 | 101 | try: 102 | import flask 103 | logger.info('Flask %s', flask.__version__) 104 | except: 105 | pass 106 | 107 | try: 108 | import pkg_resources 109 | 110 | logger.info('Pyramid %s', pkg_resources.get_distribution('pyramid').version) 111 | except: 112 | pass 113 | 114 | try: 115 | import bottle 116 | 117 | logger.info('Bottle %s; Currently Unsupported', bottle.__version__) 118 | except: 119 | pass 120 | 121 | try: 122 | import cherrypy 123 | 124 | logger.info('CherryPy %s; Currently Unsupported', cherrypy.__version__) 125 | except: 126 | pass 127 | 128 | try: 129 | import tornado 130 | 131 | logger.info('Tornado %s; Currently Unsupported', tornado.version) 132 | except: 133 | pass 134 | 135 | try: 136 | import aiohttp 137 | 138 | logger.info('Aiohttp %s; Currently Unsupported', aiohttp.__version__) 139 | except: 140 | pass 141 | 142 | 143 | def check_python_version(python_version, joined_python_version): 144 | if python_version < (2, 7, 0): 145 | logger.error('The Python Agent only supports Python version 2.7 and above for Python 2') 146 | 147 | if python_version < (3, 4, 0): 148 | logger.error('The Python Agent only supports Python version 2.4 and above for Python 3') 149 | 150 | if joined_python_version in ['2.7.5', '2.7.6']: 151 | logger.warning('Your version of Python has a regular expression bug that prevents us from fully analyzing SSRF ' 152 | 'and Command Injection attacks. It is recommended that you upgrade build versions.') 153 | 154 | 155 | def check_pip_version(): 156 | logger.info('---------- Pip ----------') 157 | 158 | try: 159 | import pip 160 | except: 161 | logger.warning('Pip is not installed!') 162 | return 163 | 164 | pip_version = pip.__version__ 165 | 166 | logger.info('Pip version: %s', pip_version) 167 | 168 | if int(pip_version[0]) < 8: 169 | logger.warning( 170 | 'An older version of pip is being used. It is recommended you upgrade to 9 or 10 for new features.') 171 | 172 | get_package_information(pip, pip_version) 173 | --------------------------------------------------------------------------------