-
Notifications
You must be signed in to change notification settings - Fork 125
Conversation
Right now the tool can handle the Given that I don't want to open more than one PR (ever) for any given web framework, I want to be sure I get the most important models right from the start. It's like going to the grocery store; I want to get everything the first time I go there. This is the list of models I want to add:
Those two seem like the most impactful. Anything else? |
Maybe also:
I'm aiming at having the most impact, rather than completeness. The most bang for the buck. |
Most of the Go team is out for the holidays now, so I'll look on the 4th January or later |
Sounds great! Thanks! Happy holidays |
You too :) |
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.
Thanks again, I should really try to get the tool working so I can use it myself.
I haven't actually looked at the models themselves, but a few comments on the generator.
ql/test/library-tests/semmle/go/frameworks/CleverGo/TaintTracking/Test.ql
Outdated
Show resolved
Hide resolved
ql/test/library-tests/semmle/go/frameworks/CleverGo/UntrustedSources/Test.ql
Outdated
Show resolved
Hide resolved
ql/test/library-tests/semmle/go/frameworks/CleverGo/UntrustedSources/Test.ql
Outdated
Show resolved
Hide resolved
Thanks for the review! This is actually not |
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.
You can see some of the other common ::Range
classes used in the newly-added Beego models (https://github.com/github/codeql-go/blob/main/ql/src/semmle/go/frameworks/Beego.qll)
Particularly HeaderWrite
and ResponseWriter
, which are used to pair up a response body with its content-type. Note that there are methods that set both simultaneously (e.g. response.JSON(xyz)
), those that explicitly set content-type (response.SetContentType("text/html"); response.Write(xyz)
) and those that set a generic HTTP header which could be Content-Type (response.SetHeader("Content-Type", "text/html"); response.Write(xyz)
)
7d31650
to
10a3bf1
Compare
Next I'm going to add I'm thinking it could look like this: private class RedirectMethods2 extends HTTP::Redirect::Range, DataFlow::CallNode {
string package;
DataFlow::Node urlNode;
RedirectMethods2() {
package = packagePath() and
(
// Function call:
this.getTarget().hasQualifiedName(package, "DoRedirect") and
urlNode = this.getArgument(2)
or
// Method on type call:
this = any(Method m | m.hasQualifiedName(package, "SomeReceiver", "Redirect")).getACall() and
urlNode = this.getArgument(1)
or
// Method on interface call:
this = any(Method m | m.implements(package, "SomeInterface", "Redirect")).getACall() and
urlNode = this.getArgument(1)
)
}
override DataFlow::Node getUrl() { result = urlNode }
// override HTTP::ResponseWriter getResponseWriter() { result.getANode() = this.getReceiver() }
override HTTP::ResponseWriter getResponseWriter() { none() }
} Questions:
Thanks! |
For header writes, |
Thanks @smowton And how does the proposed model look to you? |
Looks good to me -- @sauyon you have any further comments? |
Added the modelling for HTTP redirects. Not sure about the correctness of the inline tests query; does https://github.com/github/codeql-go/blob/9d6ac53acff98b33aacc1c7824670518e83eb4f7/ql/test/library-tests/semmle/go/frameworks/CleverGo/HttpRedirect/Test.ql actually catch what it is supposed to catch? Do they make sense? |
Once the Once a number of model kinds have been implemented for this web framework, I'll tidy up this PR to be merged (things like removing duplicate vendoring, etc.). |
Next up: private class ResponseBody extends HTTP::ResponseBody::Range {
string package;
DataFlow::CallNode call;
string contentType;
ResponseBody() {
package = packagePath() and
(
// Function call:
call.getTarget().hasQualifiedName(package, "JSON") and
this = call.getArgument(1) and
contentType = "application/json"
or
// Method on type call:
call = any(Method m | m.hasQualifiedName(package, "SomeReceiver", "JSON")).getACall() and
this = call.getArgument(0) and
contentType = "application/json"
or
// Method on interface call:
call = any(Method m | m.implements(package, "SomeInterface", "JSON")).getACall() and
this = call.getArgument(0) and
contentType = "application/json"
)
}
override string getAContentType() { result = contentType }
override HTTP::ResponseWriter getResponseWriter() { none() }
} Questions:
|
For linking response writes with other response writes and headers. If the model has another way of achieving this, omitting it is fine. |
The inline test query makes sense to me; was there a problem with it? |
Nope. Just wanted to makes sure it made sense. |
Added Next will think about |
Got a few more scenarios of |
This comment has been minimized.
This comment has been minimized.
I added more TODO:
|
One thing to note is that the A second thing to note is that this code snippet from the tests does not work (using a variable for the contentType string): // func (*Context).Blob(code int, contentType string, bs []byte) (err error)
{
bodyByte839 := source().([]byte)
contentTypeString458 := "application/json"
var rece clevergo.Context
rece.Blob(0, contentTypeString458, bodyByte839) // $contentType=application/json $responseBody=bodyByte839
} I had to change it to this (the content-type is a string passed directly as parameter in the call): // func (*Context).Blob(code int, contentType string, bs []byte) (err error)
{
bodyByte839 := source().([]byte)
var rece clevergo.Context
rece.Blob(0, "application/json", bodyByte839) // $contentType=application/json $responseBody=bodyByte839
} Maybe there's something wrong in the codeql models? |
…ted folders for each test.
4282bd4
to
8e839f3
Compare
Rebased and merging on green |
That's cool! But can you please help me solve the questions above?
|
More precisely, there are a couple questions and quirks I want to make sure
get solved before I proceed further:
#438 (comment)
#438 (comment)
|
For future reference, here is the codemill spec file for clevergo:
{
"Name": "CleverGo",
"Models": [
{
"Name": "UntrustedSources",
"Kind": "UntrustedFlowSource",
"Methods": [
{
"Name": "Self",
"Selectors": [
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-BasicAuth",
"Pos": [false, true, true, false],
"Flows": null,
"Name": "BasicAuth"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-Decode",
"Pos": [false, true, false],
"Flows": null,
"Name": "Decode"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-DefaultQuery",
"Pos": [false, false, false, true],
"Flows": null,
"Name": "DefaultQuery"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-FormValue",
"Pos": [false, false, true],
"Flows": null,
"Name": "FormValue"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-GetHeader",
"Pos": [false, false, true],
"Flows": null,
"Name": "GetHeader"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-PostFormValue",
"Pos": [false, false, true],
"Flows": null,
"Name": "PostFormValue"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-QueryParam",
"Pos": [false, false, true],
"Flows": null,
"Name": "QueryParam"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-QueryString",
"Pos": [false, true],
"Flows": null,
"Name": "QueryString"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Params-String",
"Pos": [false, false, true],
"Flows": null,
"Name": "String"
}
},
{
"Kind": "Struct",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "Struct-Context",
"TypeName": "Context",
"Fields":
{
"Params": null
}
}
},
{
"Kind": "Struct",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "Struct-Param",
"TypeName": "Param",
"Fields":
{
"Key": null,
"Value": null
}
}
},
{
"Kind": "Type",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "Type-Params",
"TypeName": "Params",
"Value": true
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "InterfaceMethod-Decoder-Decode",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "Decode"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-QueryParams",
"Pos": [false, true],
"Flows": null,
"Name": "QueryParams"
}
}]
}]
},
{
"Name": "TaintTracking",
"Kind": "TaintTracking",
"Methods": [
{
"Name": "Self",
"Selectors": [
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "Function-CleanPath",
"Pos": null,
"Flows":
{
"Blocks": [
{
"Inp": [true, false],
"Out": [false, true]
}],
"Enabled": true
},
"Name": "CleanPath"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "InterfaceMethod-Decoder-Decode",
"Pos": null,
"Flows":
{
"Blocks": [
{
"Inp": [false, true, false, false],
"Out": [false, false, true, false]
}],
"Enabled": true
},
"Name": "Decode"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "InterfaceMethod-Renderer-Render",
"Pos": null,
"Flows":
{
"Blocks": [
{
"Inp": [false, false, false, true, false, false],
"Out": [false, true, false, false, false, false]
}],
"Enabled": true
},
"Name": "Render"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Application-RouteURL",
"Pos": null,
"Flows":
{
"Blocks": [
{
"Inp": [false, true, true, false, false],
"Out": [false, false, false, true, false]
}],
"Enabled": true
},
"Name": "RouteURL"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-Context",
"Pos": null,
"Flows":
{
"Blocks": [
{
"Inp": [true, false],
"Out": [false, true]
}],
"Enabled": true
},
"Name": "Context"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Params-String",
"Pos": null,
"Flows":
{
"Blocks": [
{
"Inp": [true, false, false],
"Out": [false, false, true]
}],
"Enabled": true
},
"Name": "String"
}
}]
}]
},
{
"Name": "HttpRedirect",
"Kind": "HTTP::Redirect",
"Methods": [
{
"Name": "GetUrl",
"Selectors": [
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-Redirect",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "Redirect"
}
}]
}]
},
{
"Name": "HttpResponseBody",
"Kind": "HTTP::ResponseBody",
"Methods": [
{
"Name": "(body+ctFromFuncName):body",
"Selectors": [
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-Error",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "Error"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-HTML",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "HTML"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-HTMLBlob",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "HTMLBlob"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-JSON",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "JSON"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-JSONBlob",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "JSONBlob"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-JSONP",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "JSONP"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-JSONPBlob",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "JSONPBlob"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-JSONPCallback",
"Pos": [false, false, false, true, false],
"Flows": null,
"Name": "JSONPCallback"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-JSONPCallbackBlob",
"Pos": [false, false, false, true, false],
"Flows": null,
"Name": "JSONPCallbackBlob"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-String",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "String"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-StringBlob",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "StringBlob"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-Stringf",
"Pos": [false, false, true, true, false],
"Flows": null,
"Name": "Stringf"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-XML",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "XML"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-XMLBlob",
"Pos": [false, false, true, false],
"Flows": null,
"Name": "XMLBlob"
}
}]
},
{
"Name": "(body+ct):body",
"Selectors": [
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-Blob",
"Pos": [false, false, false, true, false],
"Flows": null,
"Name": "Blob"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-Emit",
"Pos": [false, false, false, true, false],
"Flows": null,
"Name": "Emit"
}
}]
},
{
"Name": "(body+ct):ct",
"Selectors": [
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-Blob",
"Pos": [false, false, true, false, false],
"Flows": null,
"Name": "Blob"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-Emit",
"Pos": [false, false, true, false, false],
"Flows": null,
"Name": "Emit"
}
}]
},
{
"Name": ":body",
"Selectors": [
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-Write",
"Pos": [false, true, false, false],
"Flows": null,
"Name": "Write"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-WriteString",
"Pos": [false, true, false, false],
"Flows": null,
"Name": "WriteString"
}
}]
},
{
"Name": ":ct",
"Selectors": [
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-SetContentType",
"Pos": [false, true],
"Flows": null,
"Name": "SetContentType"
}
}]
},
{
"Name": ":ctFromFuncName",
"Selectors": [
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-SetContentTypeHTML",
"Pos": [true],
"Flows": null,
"Name": "SetContentTypeHTML"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-SetContentTypeJSON",
"Pos": [true],
"Flows": null,
"Name": "SetContentTypeJSON"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-SetContentTypeText",
"Pos": [true],
"Flows": null,
"Name": "SetContentTypeText"
}
},
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-SetContentTypeXML",
"Pos": [true],
"Flows": null,
"Name": "SetContentTypeXML"
}
}]
}]
},
{
"Name": "HeaderWrite",
"Kind": "HTTP::HeaderWrite",
"Methods": [
{
"Name": "WriteKey",
"Selectors": [
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-SetHeader",
"Pos": [false, true, false],
"Flows": null,
"Name": "SetHeader"
}
}]
},
{
"Name": "WriteVal",
"Selectors": [
{
"Kind": "Func",
"Qualifier":
{
"Path": "clevergo.tech/clevergo",
"Version": "v0.5.2",
"ID": "TypeMethod-Context-SetHeader",
"Pos": [false, false, true],
"Flows": null,
"Name": "SetHeader"
}
}]
}]
}]
} |
We just use codeql-go/ql/src/semmle/go/security/Xss.qll Lines 89 to 91 in dbcf1e1
That's fine. Elsewhere we use
As this is only currently used in a not-very-nice way to link response writes and header writes together, possibly not at all. Perhaps a better interface here could be something like
No comments here.
At some point I'd like to do this, but it's very much not a priority. |
Makes sense!
👍
I'll continue to leave
Forgot to cross that out. (Done)
Forgot to cross that out. (Done) (Was referring to the vendor duplication in clevergo tests) Thanks @sauyon ! |
This is a "pilot" batch (of 1 element) for a project targeting web frameworks.
Let me know your thoughts.