Python Flask and Graphene: Incorrect request causes security issue
I'm working on a simple messaging system where two different users can send messages to each other. Right now each user polls the server requesting the newest messages using graphQL.
The issue is that when one user, user A, sends a ton of messages (graphQL mutation queries) while the other user, user B, is polling, the messages sometimes appear to have been sent by user B.
I looked deeper and found that, in the cases where the messages were misattributed, the mutation query was hit with the incorrect request (as populated in the context), and the current_user from flask_login is also wrong. The current_user seems to depend on the request. The request was one of user B's polling requests (which are not mutations.)
I traced it back a bit and found the point where the context is being populated (https://github.com/graphql-python/flask-graphql/blob/master/flask_graphql/graphqlview.py#L42) but it was difficult to figure out why it was getting the incorrect request from Flask.
My best guess is that there's something going on with Flask's request context stack, but it's tough to track down.
I should mention that authentication is done using an Authorization header and that I tried to find a case where a user was being mis-authenticated but couldn't.
This only seems to happen when I'm running flask directly using FLASK_ENV=production flask run and seems to disappear when I run it using gunicorn. Even so, it's concerning that the authentication could be so fragile.
Has anyone seen this issue before? Is there something in the Flask documentation explaining this issue, why it happens, and more importantly, why it won't happen when I use gunicorn?
Relevant code versions:
- Flask 1.0.2
- Flask-GraphQL 2.0.0
- Flask-Login 0.4.1
- Flask-SQLAlchemy 2.3.2
- graphene 2.1.3
- graphene-sqlalchemy 2.1.0
- graphql-core 2.1
- SQLAlchemy 1.2.12
Example code:
import graphene as gr
from graphql import GraphQLError
from flask_login import current_user
from ...models import Message
from .shared import MessageNode
class CreateMessage(gr.Mutation):
""" Send a message to a channel account """
message = gr.Field(MessageNode)
class Arguments:
body = gr.JSONString(required=True)
@login_required
def mutate(self, info, body):
# Make sure that the body is populated with something
if not (body.get("text") and not body.get("image")) and not (
not body.get("text") and body.get("image")
):
raise GraphQLError("Body must contain either text or an image")
new_message = Message(
body=body,
sender_account=current_user, # CURRENT USER IS INCORRECT HERE
)
db.session.add(new_message)
db.session.commit()
return CreateMessage(message=new_message)
flask graphql flask-login graphene-python flask-graphql
add a comment |
I'm working on a simple messaging system where two different users can send messages to each other. Right now each user polls the server requesting the newest messages using graphQL.
The issue is that when one user, user A, sends a ton of messages (graphQL mutation queries) while the other user, user B, is polling, the messages sometimes appear to have been sent by user B.
I looked deeper and found that, in the cases where the messages were misattributed, the mutation query was hit with the incorrect request (as populated in the context), and the current_user from flask_login is also wrong. The current_user seems to depend on the request. The request was one of user B's polling requests (which are not mutations.)
I traced it back a bit and found the point where the context is being populated (https://github.com/graphql-python/flask-graphql/blob/master/flask_graphql/graphqlview.py#L42) but it was difficult to figure out why it was getting the incorrect request from Flask.
My best guess is that there's something going on with Flask's request context stack, but it's tough to track down.
I should mention that authentication is done using an Authorization header and that I tried to find a case where a user was being mis-authenticated but couldn't.
This only seems to happen when I'm running flask directly using FLASK_ENV=production flask run and seems to disappear when I run it using gunicorn. Even so, it's concerning that the authentication could be so fragile.
Has anyone seen this issue before? Is there something in the Flask documentation explaining this issue, why it happens, and more importantly, why it won't happen when I use gunicorn?
Relevant code versions:
- Flask 1.0.2
- Flask-GraphQL 2.0.0
- Flask-Login 0.4.1
- Flask-SQLAlchemy 2.3.2
- graphene 2.1.3
- graphene-sqlalchemy 2.1.0
- graphql-core 2.1
- SQLAlchemy 1.2.12
Example code:
import graphene as gr
from graphql import GraphQLError
from flask_login import current_user
from ...models import Message
from .shared import MessageNode
class CreateMessage(gr.Mutation):
""" Send a message to a channel account """
message = gr.Field(MessageNode)
class Arguments:
body = gr.JSONString(required=True)
@login_required
def mutate(self, info, body):
# Make sure that the body is populated with something
if not (body.get("text") and not body.get("image")) and not (
not body.get("text") and body.get("image")
):
raise GraphQLError("Body must contain either text or an image")
new_message = Message(
body=body,
sender_account=current_user, # CURRENT USER IS INCORRECT HERE
)
db.session.add(new_message)
db.session.commit()
return CreateMessage(message=new_message)
flask graphql flask-login graphene-python flask-graphql
add a comment |
I'm working on a simple messaging system where two different users can send messages to each other. Right now each user polls the server requesting the newest messages using graphQL.
The issue is that when one user, user A, sends a ton of messages (graphQL mutation queries) while the other user, user B, is polling, the messages sometimes appear to have been sent by user B.
I looked deeper and found that, in the cases where the messages were misattributed, the mutation query was hit with the incorrect request (as populated in the context), and the current_user from flask_login is also wrong. The current_user seems to depend on the request. The request was one of user B's polling requests (which are not mutations.)
I traced it back a bit and found the point where the context is being populated (https://github.com/graphql-python/flask-graphql/blob/master/flask_graphql/graphqlview.py#L42) but it was difficult to figure out why it was getting the incorrect request from Flask.
My best guess is that there's something going on with Flask's request context stack, but it's tough to track down.
I should mention that authentication is done using an Authorization header and that I tried to find a case where a user was being mis-authenticated but couldn't.
This only seems to happen when I'm running flask directly using FLASK_ENV=production flask run and seems to disappear when I run it using gunicorn. Even so, it's concerning that the authentication could be so fragile.
Has anyone seen this issue before? Is there something in the Flask documentation explaining this issue, why it happens, and more importantly, why it won't happen when I use gunicorn?
Relevant code versions:
- Flask 1.0.2
- Flask-GraphQL 2.0.0
- Flask-Login 0.4.1
- Flask-SQLAlchemy 2.3.2
- graphene 2.1.3
- graphene-sqlalchemy 2.1.0
- graphql-core 2.1
- SQLAlchemy 1.2.12
Example code:
import graphene as gr
from graphql import GraphQLError
from flask_login import current_user
from ...models import Message
from .shared import MessageNode
class CreateMessage(gr.Mutation):
""" Send a message to a channel account """
message = gr.Field(MessageNode)
class Arguments:
body = gr.JSONString(required=True)
@login_required
def mutate(self, info, body):
# Make sure that the body is populated with something
if not (body.get("text") and not body.get("image")) and not (
not body.get("text") and body.get("image")
):
raise GraphQLError("Body must contain either text or an image")
new_message = Message(
body=body,
sender_account=current_user, # CURRENT USER IS INCORRECT HERE
)
db.session.add(new_message)
db.session.commit()
return CreateMessage(message=new_message)
flask graphql flask-login graphene-python flask-graphql
I'm working on a simple messaging system where two different users can send messages to each other. Right now each user polls the server requesting the newest messages using graphQL.
The issue is that when one user, user A, sends a ton of messages (graphQL mutation queries) while the other user, user B, is polling, the messages sometimes appear to have been sent by user B.
I looked deeper and found that, in the cases where the messages were misattributed, the mutation query was hit with the incorrect request (as populated in the context), and the current_user from flask_login is also wrong. The current_user seems to depend on the request. The request was one of user B's polling requests (which are not mutations.)
I traced it back a bit and found the point where the context is being populated (https://github.com/graphql-python/flask-graphql/blob/master/flask_graphql/graphqlview.py#L42) but it was difficult to figure out why it was getting the incorrect request from Flask.
My best guess is that there's something going on with Flask's request context stack, but it's tough to track down.
I should mention that authentication is done using an Authorization header and that I tried to find a case where a user was being mis-authenticated but couldn't.
This only seems to happen when I'm running flask directly using FLASK_ENV=production flask run and seems to disappear when I run it using gunicorn. Even so, it's concerning that the authentication could be so fragile.
Has anyone seen this issue before? Is there something in the Flask documentation explaining this issue, why it happens, and more importantly, why it won't happen when I use gunicorn?
Relevant code versions:
- Flask 1.0.2
- Flask-GraphQL 2.0.0
- Flask-Login 0.4.1
- Flask-SQLAlchemy 2.3.2
- graphene 2.1.3
- graphene-sqlalchemy 2.1.0
- graphql-core 2.1
- SQLAlchemy 1.2.12
Example code:
import graphene as gr
from graphql import GraphQLError
from flask_login import current_user
from ...models import Message
from .shared import MessageNode
class CreateMessage(gr.Mutation):
""" Send a message to a channel account """
message = gr.Field(MessageNode)
class Arguments:
body = gr.JSONString(required=True)
@login_required
def mutate(self, info, body):
# Make sure that the body is populated with something
if not (body.get("text") and not body.get("image")) and not (
not body.get("text") and body.get("image")
):
raise GraphQLError("Body must contain either text or an image")
new_message = Message(
body=body,
sender_account=current_user, # CURRENT USER IS INCORRECT HERE
)
db.session.add(new_message)
db.session.commit()
return CreateMessage(message=new_message)
flask graphql flask-login graphene-python flask-graphql
flask graphql flask-login graphene-python flask-graphql
edited Nov 12 at 15:36
asked Nov 9 at 21:08
maxlang
13
13
add a comment |
add a comment |
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53233291%2fpython-flask-and-graphene-incorrect-request-causes-security-issue%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53233291%2fpython-flask-and-graphene-incorrect-request-causes-security-issue%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown