From 614a19549b25dc8fc0edfa2cd0cabe613422c0bb Mon Sep 17 00:00:00 2001 From: dolevf Date: Sat, 17 Apr 2021 21:43:44 -0400 Subject: [PATCH] version 1.3.0 --- README.md | 2 + core/middleware.py | 51 +++++++----- core/security.py | 15 ++-- core/views.py | 5 +- db/solutions.py | 2 + templates/index.html | 8 ++ templates/partials/navbar.html | 2 +- templates/partials/solutions/solution_1.html | 21 +++-- templates/partials/solutions/solution_10.html | 42 +++++----- templates/partials/solutions/solution_11.html | 57 +++++++------ templates/partials/solutions/solution_12.html | 36 ++++----- templates/partials/solutions/solution_13.html | 48 ++++++----- templates/partials/solutions/solution_14.html | 55 ++++--------- templates/partials/solutions/solution_15.html | 34 ++++---- templates/partials/solutions/solution_16.html | 56 ++++++++----- templates/partials/solutions/solution_17.html | 29 +++++++ templates/partials/solutions/solution_18.html | 37 +++++++++ templates/partials/solutions/solution_2.html | 43 +++++----- templates/partials/solutions/solution_3.html | 23 +++--- templates/partials/solutions/solution_4.html | 81 ++++++++----------- templates/partials/solutions/solution_5.html | 52 ++++++------ templates/partials/solutions/solution_6.html | 63 ++++++++------- templates/partials/solutions/solution_7.html | 34 ++++---- templates/partials/solutions/solution_8.html | 63 +++++++++------ templates/partials/solutions/solution_9.html | 52 +++++------- templates/solutions.html | 6 ++ version.py | 2 +- 27 files changed, 519 insertions(+), 400 deletions(-) create mode 100644 templates/partials/solutions/solution_17.html create mode 100644 templates/partials/solutions/solution_18.html diff --git a/README.md b/README.md index 9049ed7..c90116a 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ DVGA supports Beginner and Expert level game modes, which will change the exploi * Batch Query Attack * Deep Recursion Query Attack * Resource Intensive Query Attack + * Field Duplication Attack + * Aliases based Attack * **Information Disclosure** * GraphQL Introspection * GraphiQL Interface diff --git a/core/middleware.py b/core/middleware.py index 69abe1d..b6bbfe5 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -4,39 +4,39 @@ from core.decorators import run_only_once from core import ( - helpers, - parser, + helpers, + parser, security ) # Middleware class DepthProtectionMiddleware(object): - def resolve(self, next, root, info, **kwargs): + def resolve(self, next, root, info, **kwargs): if helpers.is_level_easy(): return next(root, info, **kwargs) depth = 0 array_qry = [] - + if isinstance(info.context.json, dict): array_qry.append(info.context.json) - + elif isinstance(info.context.json, list): array_qry = info.context.json for q in array_qry: query = q.get('query', None) mutation = q.get('mutation', None) - + if query: depth = parser.get_depth(query) - + elif mutation: depth = parser.get_depth(query) - + if security.depth_exceeded(depth): raise werkzeug.exceptions.SecurityError('Query Depth Exceeded! Deep Recursion Attack Detected.') - + return next(root, info, **kwargs) class CostProtectionMiddleware(object): @@ -49,10 +49,10 @@ def resolve(self, next, root, info, **kwargs): if isinstance(info.context.json, dict): array_qry.append(info.context.json) - + elif isinstance(info.context.json, list): array_qry = info.context.json - + for q in array_qry: query = q.get('query', None) mutation = q.get('mutation', None) @@ -61,13 +61,27 @@ def resolve(self, next, root, info, **kwargs): fields_requested += parser.get_fields_from_query(query) elif mutation: fields_requested += parser.get_fields_from_query(mutation) - + if security.cost_exceeded(fields_requested): raise werkzeug.exceptions.SecurityError('Cost of Query is too high.') - + return next(root, info, **kwargs) -class processMiddleware(object): +class OpNameProtectionMiddleware(object): + @run_only_once + def resolve(self, next, root, info, **kwargs): + if helpers.is_level_easy(): + return next(root, info, **kwargs) + + opname = helpers.get_opname(info.operation) + + if opname != 'No Operation' and not security.operation_name_allowed(opname): + raise werkzeug.exceptions.SecurityError('Operation Name "{}" is not allowed.'.format(opname)) + + return next(root, info, **kwargs) + + +class processMiddleware(object): def resolve(self, next, root, info, **kwargs): if helpers.is_level_easy(): return next(root, info, **kwargs) @@ -82,7 +96,7 @@ def resolve(self, next, root, info, **kwargs): query = q.get('query', None) if security.on_denylist(query): raise werkzeug.exceptions.SecurityError('Query is on the Deny List.') - + return next(root, info, **kwargs) class IntrospectionMiddleware(object): @@ -103,8 +117,7 @@ def resolve(self, next, root, info, **kwargs): raise werkzeug.exceptions.SecurityError('GraphiQL is disabled') cookie = request.cookies.get('env') - if cookie and helpers.decode_base64(cookie) == 'graphiql:enable': + if cookie and cookie == 'graphiql:enable': return next(root, info, **kwargs) - - raise werkzeug.exceptions.SecurityError('GraphiQL Access Rejected') - \ No newline at end of file + + raise werkzeug.exceptions.SecurityError('GraphiQL Access Rejected') \ No newline at end of file diff --git a/core/security.py b/core/security.py index 14a45cb..f1f0c52 100644 --- a/core/security.py +++ b/core/security.py @@ -14,7 +14,6 @@ def simulate_load(): if count > limit: return - def is_port(port): if isinstance(port, int): if port >= 0 and port <= 65535: @@ -56,6 +55,12 @@ def on_denylist(query): return True return False +def operation_name_allowed(operation_name): + opnames_allowed = ['CreatePaste', 'getPastes', 'UploadPaste', 'ImportPaste'] + if operation_name in opnames_allowed: + return True + return False + def depth_exceeded(depth): depth_allowed = config.MAX_DEPTH if depth > depth_allowed: @@ -65,16 +70,16 @@ def depth_exceeded(depth): def cost_exceeded(qry_fields): total_cost_allowed = config.MAX_COST total_query_cost = 0 - + field_cost = { 'systemUpdate':10, } - + for field in qry_fields: if field in field_cost: total_query_cost += field_cost[field] - + if total_query_cost > total_cost_allowed: return True - + return False \ No newline at end of file diff --git a/core/views.py b/core/views.py index 9007cc1..194e1ea 100644 --- a/core/views.py +++ b/core/views.py @@ -202,7 +202,7 @@ def resolve_system_health(self, info): @app.route('/') def index(): resp = make_response(render_template('index.html')) - resp.set_cookie("env", "Z3JhcGhpcWw6ZGlzYWJsZQ==") + resp.set_cookie("env", "graphiql:disable") return resp @app.route('/about') @@ -284,7 +284,8 @@ def set_difficulty(): middleware.CostProtectionMiddleware(), middleware.DepthProtectionMiddleware(), middleware.IntrospectionMiddleware(), - middleware.processMiddleware() + middleware.processMiddleware(), + middleware.OpNameProtectionMiddleware() ] igql_middlew = [ diff --git a/db/solutions.py b/db/solutions.py index 561de74..5e49e5d 100644 --- a/db/solutions.py +++ b/db/solutions.py @@ -15,4 +15,6 @@ "partials/solutions/solution_14.html", "partials/solutions/solution_15.html", "partials/solutions/solution_16.html", + "partials/solutions/solution_17.html", + "partials/solutions/solution_18.html", ] \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 8851eb4..0b04552 100644 --- a/templates/index.html +++ b/templates/index.html @@ -30,6 +30,14 @@

Getting Started

If you are interacting with DVGA programmatically, you can set a specific game mode (such as Beginner, or Expert) by passing the HTTP Request Header X-DVGA-MODE with either Beginner or Expert as values.

If the Header is not set, DVGA will default to Easy mode.


+

Difficulty Level Explanation

+
Beginner
+

+ DVGA's Beginner level is literally the default GraphQL implementation without any restrictions, security controls, or other protections. This is what you would get out of the box in most of the GraphQL implementations without hardening, with the addition of other custom vulnerabilities. +

+
Hard
+

DVGA's Hard level is a hardened GraphQL implementation which contains a few security controls against malicious queries, such as Cost Based Analysis, Query Depth, Field De-dup checks, etc.

+

GraphQL Resources

To learn about GraphQL, and common GraphQL weaknesses and attacks, the following diff --git a/templates/partials/navbar.html b/templates/partials/navbar.html index a0e5b18..ad9fcfb 100644 --- a/templates/partials/navbar.html +++ b/templates/partials/navbar.html @@ -26,7 +26,7 @@

diff --git a/templates/partials/solutions/solution_1.html b/templates/partials/solutions/solution_1.html index b1fffbf..7c7e73b 100644 --- a/templates/partials/solutions/solution_1.html +++ b/templates/partials/solutions/solution_1.html @@ -14,20 +14,19 @@
Resources
Exploitation Solution
\ No newline at end of file diff --git a/templates/partials/solutions/solution_10.html b/templates/partials/solutions/solution_10.html index 9963169..d29b926 100644 --- a/templates/partials/solutions/solution_10.html +++ b/templates/partials/solutions/solution_10.html @@ -1,32 +1,38 @@ -

Injection :: Stored Cross Site Scripting

+

Code Execution :: OS Command Injection #1


Problem Statement

- The GraphQL mutations createPaste and importPaste allow creating and importing new pastes. The pastes may include any character without any restrictions. The pastes would then render in - the Public and Private paste pages, which would result in a Cross Site Scripting vulnerability (XSS).

+ The mutation importPaste allows escaping from the parameters and introduce a UNIX command by chaining + commands. The GraphQL resolver does not sufficiently validate the input, and passes it directly + into cURL.

Resources
-
Exploitation Solution
-