-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Insecure database queries #2892
Comments
You are right. I'd say: PRs are welcome if you are willing to help. |
Unfortunately, I don't speak Go, otherwise I definitely would've sent in PRs. It does seem like something that really needs to be prioritized, though - it's relatively little effort (for an experienced Go developer) to rule out a very large attack surface, as it'd be just a change of syntax for the most part. |
Do you have any insecure example? |
The link in the OP to queryStr := "SELECT COUNT(*) FROM `issue` "
if opts.LabelID > 0 {
queryStr += "INNER JOIN `issue_label` ON `issue`.id=`issue_label`.issue_id AND `issue_label`.label_id=" + com.ToStr(opts.LabelID)
}
baseCond := " WHERE issue.repo_id=" + com.ToStr(opts.RepoID) + " AND issue.is_closed=?"
if opts.MilestoneID > 0 {
baseCond += " AND issue.milestone_id=" + com.ToStr(opts.MilestoneID)
}
if opts.AssigneeID > 0 {
baseCond += " AND assignee_id=" + com.ToStr(opts.AssigneeID)
}
baseCond += " AND issue.is_pull=?"
This (and any query like it) needs to be parameterized. |
We don't need to discuss if that is good code from the perspective of cleanliness or style. But securitywise it's a pretty bad/naive example as it's forced to be an integer value, casted into a string (required for string concat), so it's simply not doable to inject anything here. |
@tboerger The fact that there's string-concatenation at all is the problem here. Parameterized queries exist for a very good reason - they remove the need to think about the entire code path that a value travels through to assess whether it's "injectable", by treating values as being separate from the query entirely. This is especially important for two reasons:
The whole idea of string-concatenating database queries is a historical artifact; a result of the fact that public-facing applications grew faster than databases did, and applications had to somehow use user input in queries. This has been rectified with the introduction of parameterized queries, and today, there is absolutely zero reason to still be string-concatenating queries, with all their associated security risks. |
@tboerger In this instance, that's correct, but you are wholly relying on the object you pass to Edit: @joepie91 sums this up better than I have. |
As i said, the style or current code is everything else than beautiful, but this specific case is not yet a security issue. |
This is a security issue, not a style issue. As I've explained in my previous comment. |
@joepie91 any poc maybe to illustrate your thoughts? I'd be very interested for education propose only, as i am evaluating gogs by now |
This specific example is not an security issue as you simply can not inject anything. It's just really bad practice and not clean code. |
Thanks for the comments! If anyone could explain how to make int values injectable, I would be very happy to mark this as a security issue. Update: I do try to use parameterized queries as much as possible, but failed in this case. If someone could PR a better implemented with parameterized queries, that is the best thing I can think of. Instead of talking philosophy. 😅 |
The point is not that the int values themselves are injectable, but that the moment anything upstream of this database code changes, you risk an SQLi. e.g. - queryStr += "INNER JOIN `issue_label` ON `issue`.id=`issue_label`.issue_id AND `issue_label`.label_id=" + com.ToStr(opts.LabelID)
+ queryStr += "INNER JOIN `issue_label` ON `issue`.id=`issue_label`.issue_id AND `issue_label`.label_id=" + com.ToStr(opts.LabelName) // An injection issue or type IssueStatsOptions struct {
RepoID int64
UserID int64
- LabelID int64
+ LabelID []byte // Now a UUID type; you could pass a string ID as a query param and it could potentially traverse its way into your query
MilestoneID int64 As @joepie91 points out:
The code here (and elsewhere, where the same assumptions are applied) is effectively safe by accident. That it requires such lengths to explain/address this is concerning unto itself. |
I think I may need to provide a bit of background of Gogs database design. Everything ends with UUIDs are called |
@unknwon I get that. The problem is that you are relying on never passing anything but an |
This leads to completely different topic of this thread, you're talking code reviewing now. I would like to clear my statement again:
|
For those who'd like to fix this issue and submit a PR, I think the following command will uncover all the instances of string-concatenation in queries: grep -rE "\+.*ToStr\(" . (... as well as a few false positives.) EDIT: Plus two other cases, but these may be intentional/unavoidable (although they should still be treated with care and suspicion):
|
@joepie91 asked me te have a quick look at the code. Go's database API has a perfectly usable way of using prepared statements and placeholders. But this seems to be thrown out of the window. Why? Go's query placeholders are positional. The way I see it the query concatenation seems to be a solution to building complex parameterized queries. Something that is a lot harder to do using positional placeholders. Concatting the query will move those placeholders around making it hard to keep track of the order of the parameters, so the data is added in the concatenation instead. To solve the concatenation issue, I propose one of these solutions: 1 if opts.AssigneeID > 0 {
baseCond += " AND assignee_id=" + com.ToStr(opts.AssigneeID)
} Could be rewritten to: condAssigneeId := opts.AssigneeID > 0
db.Query(`
SELECT * FROM stuff
WHERE
...
AND (? OR assignee_id=?)
...
`, ..., !condAssigneeId, opts.AssigneeID) This way, the condition is sort of ignored if 2 |
@polyfloyd it can't be written like that within Gogs. Don't think like database/sql, Gogs is using XORM. |
In GORM, we would write something like this: query := dm.Where("foo = ?", 1)
if cond == 1 {
query = query.Where("bar = ?", 2)
}
if cond == 2 {
query = query.Where("baz = ?", 3)
}
// ... Gogs uses Xorm instead of Gorm. I think it would be possible, the issue may be the JOINS. I don't know how well Gorm and Xorm handle JOINS. |
I think XORM needs proper handling of conditions for Join. Currently there is code like this: labelIDs := base.StringsToInt64s(strings.Split(opts.Labels, ","))
if len(labelIDs) > 0 {
validJoin := false
queryStr := "issue.id=issue_label.issue_id"
for _, id := range labelIDs {
if id == 0 {
continue
}
validJoin = true
queryStr += " AND issue_label.label_id=" + com.ToStr(id)
}
if validJoin {
sess.Join("INNER", "issue_label", queryStr)
}
} As labelIDs := base.StringsToInt64s(strings.Split(opts.Labels, ","))
if len(labelIDs) > 0 {
validJoin := false
cond := x.Where("issue.id=issue_label.issue_id")
for _, id := range labelIDs {
if id == 0 {
continue
}
validJoin = true
cond.Where("issue_label.label_id = ?", id)
}
if validJoin {
sess.Join("INNER", "issue_label", cond)
}
} |
Gorm would accept something like this: query := db.Where("something_id IN (?)", []int{1, 2, 3, 4, 5}) |
And XORM got |
I have created #2893 as a start to improve the parameter handling. Please take a look. If you would like to see more in it feel free to contribute. |
The problem with using prepared statements in this program is that we are no longer database agnostic. For example, in MySQL this is a prepared statement: INSERT INTO this(param) VALUES(?) While, in PgSQL this is a prepared statement: INSERT INTO this(param) VALUES($1) There is no easy way to accommodate for both of these databases without maintaining two versions of the codebase. I think the current approach is completely acceptable (UNTIL XORM ups it's |
MySQL style prepared statements were already used in the code before this issue was brought up (e.g. https://github.com/gogits/gogs/blame/master/models/issue.go#L817 added back in Nov last year). That ship has sailed, sorry. (I was also pointed at this thread by @joepie91 .) (edit: As @tboerger kindly pointed out, I was "simply wrong" about this. While the style of prepared statements used by XORM does resemble that used by MySQL, it is merely a resemblance, and the example above is translated by XORM into whatever actual syntax is used by the underlying database (see postgres_dialect.go and filter.go for details on how this is done for Postgresql.)) |
For the sake of clarity; due to the request for PRs and me not speaking Go myself, I've asked around a bit to try and find people who can help implement a fix for this. Hence the mentions above :) @barracks510 I'd imagine it should be relatively easy to abstract away this difference. I understand that XORM might not be feature-complete, but regardless, it's never acceptable to string-concatenate queries, regardless of the scenario. It's simply the wrong API to be using. If the difference is like you described, then surely it would just be a matter of replacing every |
@JesseWeinstein you are simply wrong. We are not using MySQL prepared statements, we are just using XORM which makes it database agnostic. |
As far as i can see with my pull requests there are just |
Hi, I have added join condition parameter supported on go-xorm/xorm@7d2967c |
@lunny really cool |
So #2895 should resolve all remaining issues, please review that. |
I thought gogs fully used ORM. But here indicates NO. There are inline SQL statements. |
@linquize with my pull request is pure orm again |
All queries are parameterized now, so we can close this issue now |
Looks good - thanks all for the work from everybody, even upstream :) |
What's the status on this? I can fork and work through this if not completed. It seems like it is though just not closed? |
#2892 (comment) |
Closing as fixed :) |
The Gogs codebase seems to be very inconsistent in how it generates queries. There are entire chunks of code where parameterized queries are used alongside string-concatenated queries, and this has led to SQL injection vulnerabilities in the past.
Is there any particular reason why Gogs isn't using parameterized queries everywhere? Given that all major databases now support them, it's kind of shocking to me that this isn't already the case, and makes me rather concerned about deploying Gogs on my system, security-wise.
The text was updated successfully, but these errors were encountered: