Skip to content

Commit

Permalink
Merge pull request #26 from nicholasaleks/feature/subscription
Browse files Browse the repository at this point in the history
bump version to 2.0.0
  • Loading branch information
dolevf authored Apr 4, 2022
2 parents 2d19f79 + 22a9ead commit b4dfa75
Show file tree
Hide file tree
Showing 13 changed files with 317 additions and 34 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ Damn Vulnerable GraphQL Application is an intentionally vulnerable implementatio
# About DVGA
Damn Vulnerable GraphQL is a deliberately weak and insecure implementation of GraphQL that provides a safe environment to attack a GraphQL application, allowing developers and IT professionals to test for vulnerabilities.

## DVGA Operation Support
- Queries
- Mutations
- Subscriptions

DVGA has numerous flaws, such as Injections, Code Executions, Bypasses, Denial of Service, and more. See the full list under the [Scenarios](#scenarios) section.

# Operation Modes
Expand Down Expand Up @@ -65,8 +70,11 @@ The following Python3 libraries are required:
* Python3 (3.6+)
* Flask
* Flask-SQLAlchemy
* Flask-Sockets
* Gevent
* Graphene
* Graphene-SQLAlchemy
* Rx

See [requirements.txt](requirements.txt) for dependencies.

Expand Down
23 changes: 18 additions & 5 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,34 @@
from core.helpers import initialize
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_sockets import Sockets
from graphql_ws.gevent import GeventSubscriptionServer
import logging
import logging.handlers as handlers

app = Flask(__name__, static_folder="static/")
app.secret_key = urandom(24)
app.config["SQLALCHEMY_DATABASE_URI"] = config.SQLALCHEMY_DATABASE_URI
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = config.SQLALCHEMY_TRACK_MODIFICATIONS
app.config["UPLOAD_FOLDER"] = config.WEB_UPLOADDIR

sockets = Sockets(app)


app.app_protocol = lambda environ_path_info: 'graphql-ws'

db = SQLAlchemy(app)

if __name__ == '__main__':
sys.setrecursionlimit(100000)

initialize()

from core.views import *
app.run(debug = config.WEB_DEBUG,
host = config.WEB_HOST,
port = config.WEB_PORT,
threaded=True,
use_evalex=False)
from gevent import pywsgi
from geventwebsocket.handler import WebSocketHandler
from version import VERSION

server = pywsgi.WSGIServer((config.WEB_HOST, int(config.WEB_PORT)), app, handler_class=WebSocketHandler)
print("DVGA Server Version: {version} Running...".format(version=VERSION))
server.serve_forever()
7 changes: 6 additions & 1 deletion core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

from app import db
from core import helpers
from sqlalchemy import event

from rx import Observable

# Models
class User(db.Model):
Expand Down Expand Up @@ -36,6 +39,7 @@ class Owner(db.Model):
name = db.Column(db.String)
paste = db.relationship('Paste', lazy='dynamic')


class Paste(db.Model):
__tablename__ = 'pastes'
id = db.Column(db.Integer, primary_key=True)
Expand All @@ -56,4 +60,5 @@ def create_paste(cls, **kw):
obj = cls(**kw)
db.session.add(obj)
db.session.commit()
return obj

return obj
12 changes: 12 additions & 0 deletions core/paste_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from models import Paste
from rx import Observable, Observer

class PasteService(Observer):
users = None
def on_next(self):
self.users = [a.json() for a in UserModel.query.all()]

def on_completed(self):
return self.users
def on_error(self):
return {"message":"Error occured"}
113 changes: 113 additions & 0 deletions core/view_override.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from flask_graphql import GraphQLView
from graphql_server import (HttpQueryError, run_http_query, FormattedResult)
from functools import partial
from flask import Response, request
from rx import AnonymousObservable


def format_execution_result(execution_result, format_error,):
status_code = 200
if execution_result:
#print(type(execution_result))
target_result = None

def override_target_result(value):
nonlocal target_result
target_result = value

if isinstance(execution_result, AnonymousObservable):
target = execution_result.subscribe(on_next=lambda value: override_target_result(value))
target.dispose()
response = target_result
elif execution_result.invalid:
status_code = 400
response = execution_result.to_dict(format_error=format_error)
else:
response = execution_result.to_dict(format_error=format_error)
else:
response = None
#print(response)
return FormattedResult(response, status_code)

def encode_execution_results(
execution_results,
format_error,
is_batch,
encode,
):

responses = [
format_execution_result(execution_result, format_error)
for execution_result in execution_results
]
result, status_codes = zip(*responses)
status_code = max(status_codes)

if not is_batch:
result = result[0]

return encode(result), status_code

class OverriddenView(GraphQLView):
def dispatch_request(self):
try:
request_method = request.method.lower()
data = self.parse_body()

show_graphiql = request_method == 'get' and self.should_display_graphiql()
catch = show_graphiql

pretty = self.pretty or show_graphiql or request.args.get('pretty')

extra_options = {}
executor = self.get_executor()
if executor:
# We only include it optionally since
# executor is not a valid argument in all backends
extra_options['executor'] = executor

execution_results, all_params = run_http_query(
self.schema,
request_method,
data,
query_data=request.args,
batch_enabled=self.batch,
catch=catch,
backend=self.get_backend(),

# Execute options
root=self.get_root_value(),
context=self.get_context(),
middleware=self.get_middleware(),
**extra_options
)

result, status_code = encode_execution_results(
execution_results,
is_batch=isinstance(data, list),
format_error=self.format_error,
encode=partial(self.encode, pretty=pretty)

)

if show_graphiql:
return self.render_graphiql(
params=all_params[0],
result=result
)

return Response(
result,
status=status_code,
content_type='application/json'
)

except HttpQueryError as e:
return Response(
self.encode({
'errors': [self.format_error(e)]
}),
status=e.status_code,
headers=e.headers,
content_type='application/json'
)
65 changes: 54 additions & 11 deletions core/views.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import graphene

import asyncio
from core.directives import *
from db.solutions import solutions as list_of_solutions
from flask_cors import CORS, cross_origin
from rx import Observable
from rx.subjects import Subject

from sqlalchemy import create_engine, text
from flask import (
request,
render_template,
make_response,
session
)

from flask_sockets import Sockets

from graphql.backend import GraphQLCoreBackend
from graphql_ws.gevent import GeventSubscriptionServer

from flask_graphql import GraphQLView
from sqlalchemy.sql import text
from graphene_sqlalchemy import (
Expand All @@ -23,6 +32,8 @@
helpers,
middleware
)
from sqlalchemy import event
from core.view_override import OverriddenView

from core.models import (
Owner,
Expand All @@ -32,6 +43,7 @@
)

from version import VERSION
from config import WEB_HOST, WEB_PORT

# SQLAlchemy Types
class UserObject(SQLAlchemyObjectType):
Expand Down Expand Up @@ -60,14 +72,10 @@ def resolve_ip_addr(self, info):
return self.ip_addr


# def resolve_content(self, info):
# print(info)
# return self.content

class OwnerObject(SQLAlchemyObjectType):
class Meta:
model = Owner

class CreatePaste(graphene.Mutation):
paste = graphene.Field(lambda:PasteObject)

Expand Down Expand Up @@ -191,6 +199,24 @@ class Mutations(graphene.ObjectType):
upload_paste = UploadPaste.Field()
import_paste = ImportPaste.Field()


global_event = Subject()

@event.listens_for(Paste, 'after_insert')
def new_paste(mapper,cconnection,target):
global_event.on_next(target)


class Subscription(graphene.ObjectType):

paste = graphene.Field(PasteObject, id=graphene.Int(), title=graphene.String())

def resolve_paste(self, info):

return global_event.map(lambda i: i)



class Query(graphene.ObjectType):
pastes = graphene.List(PasteObject, public=graphene.Boolean(), limit=graphene.Int(), filter=graphene.String())
paste = graphene.Field(PasteObject, id=graphene.Int(), title=graphene.String())
Expand Down Expand Up @@ -332,8 +358,8 @@ def difficulty(level):


@app.context_processor
def get_version():
return dict(version=VERSION)
def get_server_info():
return dict(version=VERSION, host=WEB_HOST, port=WEB_PORT)

@app.before_request
def set_difficulty():
Expand All @@ -347,7 +373,17 @@ def set_difficulty():
if session.get('difficulty') == None:
helpers.set_mode('easy')

schema = graphene.Schema(query=Query, mutation=Mutations, directives=[ShowNetworkDirective, SkipDirective, DeprecatedDirective])
schema = graphene.Schema(query=Query, mutation=Mutations, subscription=Subscription, directives=[ShowNetworkDirective, SkipDirective, DeprecatedDirective])

subscription_server = GeventSubscriptionServer(schema)

sockets = Sockets(app)

@sockets.route('/subscriptions')
def echo_socket(ws):
subscription_server.handle(ws)
return []


gql_middlew = [
middleware.CostProtectionMiddleware(),
Expand All @@ -361,16 +397,23 @@ def set_difficulty():
middleware.IGQLProtectionMiddleware()
]

app.add_url_rule('/graphql', view_func=GraphQLView.as_view(
class CustomBackend(GraphQLCoreBackend):
def __init__(self, executor=None):
super().__init__(executor)
self.execute_params['allow_subscriptions'] = True

app.add_url_rule('/graphql', view_func=OverriddenView.as_view(
'graphql',
schema=schema,
middleware=gql_middlew,
backend=CustomBackend(),
batch=True
))

app.add_url_rule('/graphiql', view_func=GraphQLView.as_view(
app.add_url_rule('/graphiql', view_func=OverriddenView.as_view(
'graphiql',
schema = schema,
backend=CustomBackend(),
graphiql = True,
middleware = igql_middlew
))
Expand Down
11 changes: 10 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,33 @@ certifi==2020.12.5
chardet==4.0.0
click==7.1.2
Flask==1.1.2
Flask-Cors==3.0.10
Flask-GraphQL==2.0.1
Flask-GraphQL-Auth==1.3.2
Flask-Sockets==0.2.1
Flask-SQLAlchemy==2.4.4
gevent==21.12.0
gevent-websocket==0.10.1
graphene==2.1.8
graphene-sqlalchemy==2.3.0
graphql-core==2.3.2
graphql-relay==2.0.1
graphql-server-core==1.2.0
graphql-ws==0.4.4
greenlet==1.1.2
idna==2.10
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
promise==2.3
PyJWT==2.3.0
Pypubsub==4.0.3
requests==2.25.1
Rx==1.6.1
singledispatch==3.4.0.3
six==1.15.0
SQLAlchemy==1.3.22
urllib3==1.26.3
Werkzeug==1.0.1

zope.event==4.5.0
zope.interface==5.4.0
Loading

0 comments on commit b4dfa75

Please sign in to comment.