-
Notifications
You must be signed in to change notification settings - Fork 31
INTPYTHON-827 Prevent Value strings and model update values from being interpreted as expressions #445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
| if is_direct_value(value): | ||
| prepared = {"$literal": prepared} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it necessary to wrap all values? Are there problems with other types? Our logic in Value() only wraps list/int/str.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A unit test (test_update_dollar_prefix_in_value_expression) shows the issue. Because we could update passing an expression, and the expression could be Value("$update")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I meant: Is it necessary to wrap all direct values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
understood, 🤔 . Will try to make a test and try to inject something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, 🤔. maybe only string should be wrapped, not any kind of value.
EDIT
it is possible:
def test_update_dict(self):
obj = Author.objects.create(name="foobar")
obj.name = Value({"$concat": ["$name", "-", "$name"]})
obj.save()
obj.refresh_from_db()
self.assertEqual(obj.name, {"$concat": ["$name", "-", "$name"]})this tails fails and the name was update as foobar-foobar
I think we should scape any kind of literal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That seems sufficient to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another case:
def test_update_dictvalue(self):
obj = Author.objects.create(name="foobar", data={})
obj.data = {"$concat": ["$name", "-", "$name"]}
obj.save()
obj.refresh_from_db()
self.assertEqual(obj.data, {"$concat": ["$name", "-", "$name"]})If the field is a JSONField. Any kind of type that could be a MongoDB query, could be exploitable.
I think wrapping al values will bring us a safe net.
68e4632 to
c1b41ac
Compare
51c8912 to
9ddb1ca
Compare
| # appears in $project. | ||
| if isinstance(value, (list, int, str, dict, tuple)) and as_expr: | ||
| # Wrap lists, numbers, strings, dict and tuple in $literal to avoid ambiguity when Value | ||
| # is used in queries' aggregate or update_many's $set. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, you replaced $project with aggregate. Is there another place in aggregate besides $project where this could be an issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, in place like $group or $match (for having or filter). Do we need a test for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, just wondering.
| ) | ||
| prepared = field.get_db_prep_save(value, connection=self.connection) | ||
| if hasattr(value, "as_mql"): | ||
| if is_direct_value(value): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still question whether the $literal escaping is overly broad. Better safe than sorry, sure, but it decreases readability a bit and makes queries larger. But I'm not sure how to compare the tradeoffs between some more complicated logic (isinstance() CPU time) and leaving it as is. It seems if we did wrapping of only the types that Value() wraps, it should be safe, unless the logic in Value() is deficient. And are there any string values besides those that start with $ that could be problematic? Perhaps to be discussed in chat tomorrow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 I agree. Seeing $literal everywhere is annoying, but Value's resolver is probably covering the most common types used in queries. On the other hand, implementing the same escaping logic that Value uses is not a big deal.
9ddb1ca to
cb67abe
Compare
This paves the way for fixing a problem with update in INTPYTHON-827.
timgraham
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments to explain a few of my edits.
e8b1396 to
52f0d44
Compare
52f0d44 to
215d3be
Compare
INTPYTHON-827
Summary
This PR fixes unsafe handling of
$-prefixed values duringsave()andupdate()operations in the Django MongoDB backend.Values like
{"$concat": [...]}or("$name", "-", "$name")were previously interpreted as MongoDB operators instead of being stored literally.The patch ensures that all such values are wrapped with
$literalwhen appropriate, preventing unintended expression or operator injection.Changes in this PR
$literalwhere needed.Test Plan
$-prefixed values are safely stored and not interpreted as expressions.Value()wrappers.$-prefixed) values are unaffected (basic unit test from Django)Checklist
Checklist for Author
Checklist for Reviewer @Jibola @aclark4life @timgraham
Focus Areas for Reviewer
Pay attention to the logic handling
$literalwrapping in value compilation.