Coder Social home page Coder Social logo

vampi's Introduction

VAmPI

The Vulnerable API (Based on OpenAPI 3) vampi

Docker Image CI Docker Pulls

VAmPI is a vulnerable API made with Flask and it includes vulnerabilities from the OWASP top 10 vulnerabilities for APIs. It was created as I wanted a vulnerable API to evaluate the efficiency of tools used to detect security issues in APIs. It includes a switch on/off to allow the API to be vulnerable or not while testing. This allows to cover better the cases for false positives/negatives. VAmPI can also be used for learning/teaching purposes. You can find a bit more details about the vulnerabilities in erev0s.com.

Features

  • Based on OWASP Top 10 vulnerabilities for APIs.
  • OpenAPI3 specs and Postman Collection included.
  • Global switch on/off to have a vulnerable environment or not.
  • Token-Based Authentication (Adjust lifetime from within app.py)
  • Available Swagger UI to directly interact with the API

VAmPI's flow of actions is going like this: an unregistered user can see minimal information about the dummy users included in the API. A user can register and then login to be allowed using the token received during login to post a book. For a book posted the data accepted are the title and a secret about that book. Each book is unique for every user and only the owner of the book should be allowed to view the secret.

A quick rundown of the actions included can be seen in the following table:

Action Path Details
GET /createdb Creates and populates the database with dummy data
GET / VAmPI home
GET /users/v1 Displays all users with basic information
GET /users/v1/_debug Displays all details for all users
POST /users/v1/register Register new user
POST /users/v1/login Login to VAmPI
GET /users/v1/{username} Displays user by username
DELETE /users/v1/{username} Deletes user by username (Only Admins)
PUT /users/v1/{username}/email Update a single users email
PUT /users/v1/{username}/password Update users password
GET /books/v1 Retrieves all books
POST /books/v1 Add new book
GET /books/v1/{book} Retrieves book by title along with secret

For more details you can either run VAmPI and visit http://127.0.0.1:5000/ui/ or use a service like the swagger editor supplying the OpenAPI specification which can be found in the directory openapi_specs.

List of Vulnerabilities

  • SQLi Injection
  • Unauthorized Password Change
  • Broken Object Level Authorization
  • Mass Assignment
  • Excessive Data Exposure through debug endpoint
  • User and Password Enumeration
  • RegexDOS (Denial of Service)
  • Lack of Resources & Rate Limiting
  • JWT authentication bypass via weak signing key

Run it

It is a Flask application so in order to run it you can install all requirements and then run the app.py. To install all requirements simply run pip3 install -r requirements.txt and then python3 app.py.

Or if you prefer you can also run it through docker or docker compose.

Run it through Docker

docker run -p 5000:5000 erev0s/vampi:latest

[Note: if you run Docker on newer versions of the MacOS, use -p 5001:5000 to avoid conflicting with the AirPlay Receiver service. Alternatively, you could disable the AirPlay Receiver service in your System Preferences -> Sharing settings.]

Run it through Docker Compose

docker-compose contains two instances, one instance with the secure configuration on port 5001 and another with insecure on port 5002:

docker-compose up -d

Available Swagger UI ๐Ÿš€

Visit the path /ui where you are running the API and a Swagger UI will be available to help you get started!

http://127.0.0.1:5000/ui/

Customizing token timeout and vulnerable environment or not

If you would like to alter the timeout of the token created after login or if you want to change the environment not to be vulnerable then you can use a few ways depending how you run the application.

  • If you run it like normal with python3 app.py then all you have to do is edit the alive and vuln variables defined in the app.py itself. The alive variable is measured in seconds, so if you put 100, then the token expires after 100 seconds. The vuln variable is like boolean, if you set it to 1 then the application is vulnerable, and if you set it to 0 the application is not vulnerable.
  • If you run it through Docker, then you must either pass environment variables to the docker run command or edit the Dockerfile and rebuild.
    • Docker run example: docker run -d -e vulnerable=0 -e tokentimetolive=300 -p 5000:5000 erev0s/vampi:latest

      • One nice feature to running it this way is you can startup a 2nd container with vulnerable=1 on a different port and flip easily between the two.
    • In the Dockerfile you will find two environment variables being set, the ENV vulnerable=1 and the ENV tokentimetolive=60. Feel free to change it before running the docker build command.

Picture from freepik - www.freepik.com

vampi's People

Contributors

aaronhmiller avatar davidbolvansky avatar erev0s avatar mathew-jose avatar mikeacjones avatar ross-forallsecure avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vampi's Issues

flask-sqlalchemy incompatible version - with solution -

Linux version: Ubuntu 22.04.1
Summary: flask-sqlalchemy version when installing requirements.txt is incompatible and raises error shown below:

AttributeError: module 'sqlalchemy' has no attribute 'all'. Did you mean: 'file'?

Following updated requirements.txt solves this error:

connexion~=2.7.0
flask~=2.2
flask-sqlalchemy>=3.0.2
jsonschema~=3.2.0
sqlalchemy
swagger-ui-bundle
PyJWT~=1.7.1
markupsafe==2.1.1

Authentication missing in OAS

Your OAS file is missing authentication. Looking at your OAS with Swagger, all paths appear to be unsecured and do not require authentication, even though they do. Can you fix this?

Incomplete response schemas in OpenAPI specification

Hello,
some operations in the OpenAPI specification does not provide a response schema, e.g., in the GET /users/v1 operation the response schema for the 200 status code is empty ({}), but it should be an array of user objects.
Could you please update the specification with the response schemas?

Good to have documentation of vulnerabilities

Hi,

I have seen your readme but i feel we should have some ref table showing which exploit top 10 category is present in which endpoint. Because i mapped one specific endpoint to available vulnerabilities but then same vulnerability my colleague mapped to another and we don't have any clarity which one is a valid endpoint to test those vulnerabilities.

Also having proper solution will be great. Let me know if need any help !

Thanks,
Janibasha

More features!

First of all, man, your work is awesome!
I'm using it to teach my students how to make web more secured.

I want to advise some ides, what can be used in continuation of your fantastic project:

What about adding some kind of RCE and docker container hardening?
For example, your awesome app can started as container inside VM in 4 variants:

  1. Vulnerable APP + Vulnerable container, including RCE and non hardened docker container = RCE -> docker escape on VM
  2. Vulnerable APP + Not Vulnerable container, including RCE and hardened docker container = RCE, but no docker escape on VM
  3. Not Vulnerable APP + Vulnerable container, no RCE and hardened docker container = no docker escape, but container may be DOSed.
  4. Not Vulnerable APP + Not Vulnerable container, no RCE and hardened docker container = All Ok!

According to this:
https://github.com/OWASP/Docker-Security
https://github.com/OWASP/Docker-Security/blob/main/dist/owasp-docker-security.pdf
https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html

Suggestions for README

Love this project! Thank you.
Opening an issue to use w/ a PR I'm about to submit.

To make it even easier and more powerful to use as a teaching tool, it is handy to run one instance of VAmPI securely (vulnerable=0) and another insecure instance running in parallel on a different port (vulnerable=1). Your use of ENV within the Dockerfile makes this dead simple and adjustments to the README are all that is needed.

Also ran into a conflict w/ port 5000 on Mac OSX, so put in a note about two ways to resolve that.

Admin parameter is boolean, but for mass assignment is string

Hello,
the admin parameter of the user is store as boolean in the system, but in register_user is it treated like a string. Typically, mass assignment vulnerabilities are present because a REST API framework directly maps an HTTP parameter to an attribute in the system. To make the example closer to reality, try to mimic this behavior by accepting a boolean parameter rather than a string.

Markupsafe version updates cause import error

Following the default requirements.txt causes error when run the docker image

Traceback (most recent call last):
  File "app.py", line 1, in <module>
    from config import vuln_app
  File "/vampi/config.py", line 2, in <module>
    import connexion
  File "/usr/local/lib/python3.7/site-packages/connexion/__init__.py", line 5, in <module>
    from .apis import AbstractAPI  # NOQA
  File "/usr/local/lib/python3.7/site-packages/connexion/apis/__init__.py", line 1, in <module>
    from .abstract import AbstractAPI  # NOQA
  File "/usr/local/lib/python3.7/site-packages/connexion/apis/abstract.py", line 16, in <module>
    from ..spec import Specification
  File "/usr/local/lib/python3.7/site-packages/connexion/spec.py", line 5, in <module>
    import jinja2
  File "/usr/local/lib/python3.7/site-packages/jinja2/__init__.py", line 12, in <module>
    from .environment import Environment
  File "/usr/local/lib/python3.7/site-packages/jinja2/environment.py", line 25, in <module>
    from .defaults import BLOCK_END_STRING
  File "/usr/local/lib/python3.7/site-packages/jinja2/defaults.py", line 3, in <module>
    from .filters import FILTERS as DEFAULT_FILTERS  # noqa: F401
  File "/usr/local/lib/python3.7/site-packages/jinja2/filters.py", line 13, in <module>
    from markupsafe import soft_unicode
ImportError: cannot import name 'soft_unicode' from 'markupsafe' (/usr/local/lib/python3.7/site-packages/markupsafe/__init__.py)

Add version requirements on markupsafe==2.0.2 package solves the issue, but I haven't checked if the docker can function normally.

get_users return repr format when vulnerable=0

The get_user method in the models/user_model.py return the _ repr _ format when vulnerable=0.

Example

$ curl "http://localhost:5000/users/v1/name1"
<User(name=name1, [email protected])>

This not happen when the vulnerable environment is True, because in that scenario the response is a formatted string, meanwhile in this one it returns the first user object found:

78   else:
79     fin_query = User.query.filter_by(username=username).first()

Possible solution:

We can take advance of the json class method to return the user data:

      else:
          user = User.query.filter_by(username=username).first()
          fin_query = user.json() if user else None
      return fin_query

Note: Calling .json() of a NoneType object could raise an error, so we must check if exists first.

But the .json() class method encloses the value in single quotes. That's not the standard json syntax we are looking for. A better solution could be like this:

      else:
          user = User.query.filter_by(username=username).first()
          fin_query = str(User.json(user)).replace("'",'"') if user else None
      return fin_query

Result:

$ curl "http://localhost:5000/users/v1/name1" # not vuln
{"username": "name1", "email": "[email protected]"}

$ curl "http://localhost:5001/users/v1/name1" # vuln
{"username": "name1", "email": "[email protected]"}

Unable to create DB or write anything into DB.

The stake-trace appears as below while trying to perform any write operation.

##################

  • File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/default.py", line 736, in do_execute

     
        def do_executemany(self, cursor, statement, parameters, context=None):
            cursor.executemany(statement, parameters)
     
        def do_execute(self, cursor, statement, parameters, context=None):
            cursor.execute(statement, parameters)
     
        def do_execute_no_params(self, cursor, statement, context=None):
            cursor.execute(statement)
     
        def is_disconnect(self, e, connection, cursor):
    sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) attempt to write a readonly database [SQL: DROP TABLE books] (Background on this error at: https://sqlalche.me/e/14/e3q8)

    This is the Copy/Paste friendly version of the traceback. You can also paste this traceback into a gist:

    <textarea cols="50" rows="10" name="code" readonly>Traceback (most recent call last): File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 1901, in _execute_context cursor, statement, parameters, context File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/default.py", line 736, in do_execute cursor.execute(statement, parameters) sqlite3.OperationalError: attempt to write a readonly database

    The above exception was the direct cause of the following exception:

    Traceback (most recent call last):
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2464, in call
    return self.wsgi_app(environ, start_response)
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2450, in wsgi_app
    response = self.handle_exception(e)
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1867, in handle_exception
    reraise(exc_type, exc_value, tb)
    File "/usr/local/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
    File "/usr/local/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functionsrule.endpoint
    File "/usr/local/lib/python3.7/site-packages/connexion/decorators/decorator.py", line 48, in wrapper
    response = function(request)
    File "/usr/local/lib/python3.7/site-packages/connexion/decorators/uri_parsing.py", line 144, in wrapper
    response = function(request)
    File "/usr/local/lib/python3.7/site-packages/connexion/decorators/parameter.py", line 121, in wrapper
    return function(**kwargs)
    File "/vampi/api_views/main.py", line 7, in populate_db
    db.drop_all()
    File "/usr/local/lib/python3.7/site-packages/flask_sqlalchemy/init.py", line 1102, in drop_all
    self._execute_for_all_tables(app, bind, 'drop_all')
    File "/usr/local/lib/python3.7/site-packages/flask_sqlalchemy/init.py", line 1086, in _execute_for_all_tables
    op(bind=self.get_engine(app, bind), **extra)
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/sql/schema.py", line 4960, in drop_all
    ddl.SchemaDropper, self, checkfirst=checkfirst, tables=tables
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 3228, in _run_ddl_visitor
    conn._run_ddl_visitor(visitorcallable, element, **kwargs)
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 2211, in _run_ddl_visitor
    visitorcallable(self.dialect, self, **kwargs).traverse_single(element)
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/sql/visitors.py", line 524, in traverse_single
    return meth(obj, **kw)
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/sql/ddl.py", line 1029, in visit_metadata
    _ignore_sequences=seq_coll,
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/sql/visitors.py", line 524, in traverse_single
    return meth(obj, **kw)
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/sql/ddl.py", line 1102, in visit_table
    self.connection.execute(DropTable(table))
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 1380, in execute
    return meth(self, multiparams, params, _EMPTY_EXECUTION_OPTS)
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/sql/ddl.py", line 81, in _execute_on_connection
    self, multiparams, params, execution_options
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 1478, in _execute_ddl
    compiled,
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 1944, in execute_context
    e, statement, parameters, cursor, context
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 2125, in handle_dbapi_exception
    sqlalchemy_exception, with_traceback=exc_info[2], from
    =e
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/util/compat.py", line 211, in raise

    raise exception
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 1901, in _execute_context
    cursor, statement, parameters, context
    File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/default.py", line 736, in do_execute
    cursor.execute(statement, parameters)
    sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) attempt to write a readonly database
    [SQL:
    DROP TABLE books]
    (Background on this error at: https://sqlalche.me/e/14/e3q8)</textarea>
    ##################

  • greenlet now requires gcc and python headers

    I'm running a relatively stock, updated Ubuntu 20.04 box and I'm hitting a docker build issue. One of the python dependencies pulls in a package called greenlet, which is compiling against the python development headers and using the g++ compile.

    Share attack scripts

    Hi,

    I can see doc says this demo app supports multiple attacks but i don't see attack payloads. So can you please share them so i can also test these attacks.

    Thanks,
    Jani

    endpoint /users/v1/register behaves differently depending on docker image (dockerhub vs dockerfile)

    Hello,

    Firstly, thank you for this very pleasant project!

    I've encountered an ambiguity multiple times during my tests on the /users/v1/register endpoint. When using the image from Docker Hub, I don't get the same result when attempting to promote myself to admin on the API. Specifically, I am unable to perform this privilege escalation from the container linked to the image on Docker Hub, but only through the one I manually build via the project's Dockerfile.

    Here are the data I'm sending to the API:

    jsonCopy code

    { "username": "test2", "password": "test", "email": "[email protected]", "admin": "True" }

    I want to mention that I also tested with "admin": true.

    Here are my tests in more detail with Postman:

    Test using the project's Dockerfile

    • User Creation 1

    • Debug Mode: It's observed that the newly added user is an admin! 2

    Test using the image stored on Docker Hub

    • User Creation 3

    • Debug Mode: It's observed that the newly added user is not an admin... 4

    Is this an issue with the image not being updated on Docker Hub? (Last release: 4 months ago: Docker Hub Link)

    I am available if you need more information.

    Best regards,

    Archidote

    Recommend Projects

    • React photo React

      A declarative, efficient, and flexible JavaScript library for building user interfaces.

    • Vue.js photo Vue.js

      ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

    • Typescript photo Typescript

      TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

    • TensorFlow photo TensorFlow

      An Open Source Machine Learning Framework for Everyone

    • Django photo Django

      The Web framework for perfectionists with deadlines.

    • D3 photo D3

      Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

    Recommend Topics

    • javascript

      JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

    • web

      Some thing interesting about web. New door for the world.

    • server

      A server is a program made to process requests and deliver data to clients.

    • Machine learning

      Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

    • Game

      Some thing interesting about game, make everyone happy.

    Recommend Org

    • Facebook photo Facebook

      We are working to build community through open source technology. NB: members must have two-factor auth.

    • Microsoft photo Microsoft

      Open source projects and samples from Microsoft.

    • Google photo Google

      Google โค๏ธ Open Source for everyone.

    • D3 photo D3

      Data-Driven Documents codes.