-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Go: fasthttp #14123
Go: fasthttp #14123
Conversation
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.
CodeQL found more than 10 potential problems in the proposed changes. Check the Files changed tab for more details.
Hi @amammad! Thank you so much for putting together models for the Is this PR ready for review by us? For starters, I have approved the workflow runs and there seem to be some failures related to your new tests there that you may want to look into. If the PR is ready for review, could you update the description with details of your expectations regarding which queries the new models do and do not support? |
@mbg :) Thanks for the clarification. I don't know if I understood exactly what you mean about the description, so please let me know if I didn't update the description of this pull request correctly. |
@mbg tests are ok on my side I don't have any idea why it failed here. |
@mbg I think it is OK now. |
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.
It looks like the tests still aren't quite right. See my comment about making sure that you are using Go 1.21 and that you have built the autobuilder/extractor with Go 1.21.
I have also made a suggestion for the approach taken by the tests and a minor comment about the format of the changelog entry.
I have given the models a quick glance over, but still need to review them thoroughly.
go/ql/lib/change-notes/2023-18-09--add-support-for-fasthttp-framework.md
Outdated
Show resolved
Hide resolved
go/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalFlowStep.expected
Outdated
Show resolved
Hide resolved
go/ql/test/library-tests/semmle/go/frameworks/Fasthttp/fasthttp.ql
Outdated
Show resolved
Hide resolved
exists(Method m | | ||
m.hasQualifiedName(packagePath(), "Response", methodName) and | ||
call = m.getACall() and | ||
this = call.getArgument(0) | ||
or | ||
m.hasQualifiedName(packagePath(), "RequestCtx", ["Success", "SuccessString"]) and | ||
call = m.getACall() and | ||
this = call.getArgument(1) | ||
) and | ||
methodName = | ||
[ | ||
"AppendBody", "AppendBodyString", "SetBody", "SetBodyRaw", "SetBodyStream", | ||
"SetBodyString", "Success", "SuccessString" | ||
] | ||
or | ||
exists(Method write, DataFlow::CallNode writeCall | | ||
write.hasQualifiedName("io", "Writer", "Write") and | ||
writeCall = write.getACall() and | ||
ResponseBodyWriterFlow::flowsTo(writeCall.getReceiver()) and | ||
this = writeCall.getArgument(0) | ||
) and | ||
methodName = "BodyWriter" |
Check warning
Code scanning / CodeQL
Var only used in one side of disjunct. Warning
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.
Should writeCall
be renamed to call
, to use the field? Does that make sense with how call
is used in getResponseWriter
?
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 wanted to ask you about this part which is related to DataFlow::SimpleGlobal
which you wrote before, I couldn't use call
in this part, I must do something like this for the isSource
predicate of DataFlow::SimpleGlobal
private predicate responseBodyWriterResult(DataFlow::Node src) {
exists(Method responseBodyWriter |
responseBodyWriter.hasQualifiedName(packagePath(), "Response", "BodyWriter") and
src = responseBodyWriter.getACall().getResult(0)
)
}
to
private predicate responseBodyWriterResult(DataFlow::Node src) {
exists(Method responseBodyWriter |
responseBodyWriter.hasQualifiedName(packagePath(), "Response", "BodyWriter") and
call = responseBodyWriter.getACall() and
src = call.getResult(0)
)
}
and somehow connect this call to the call inside the ResponseBody
class.
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 see. DataFlow::SimpleGlobal
isn't suitable then, as you need the beginning and the end of the path, and I don't think it can give that. I will double check. The other options are DataFlow::Global
, which seems like too much, or local flow, which stays within a function. I see that the other libraries that do this kind of thing use local flow. But I remember from when I recommended SimpleGlobal
that I searched GitHub and found lots of examples of the return value of Response.BodyWriter
being passed to other functions before Write
was called on it.
I think you should start with local flow (either getASuccessor*
or DataFlow::localFlow()
and maybe use MRVA to see if that means you are missing a lot. (I mean run MRVA with a query like can't find local flow to a call to Write()
.
Btw, looking at the search results I see that the writer is often passed as the first argument to fmt.Fprintf
, so you might want to include that as an alternative destination to io.Writer.Write()
.
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! I see a lot of Response.BodyWriter()
Also are using other than write(sink)
or fmt.Fprintf(Response.BodyWriter(),template,sink..)
so I don't know how to handle all of these now :)
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.
An idea I just had: if a function has arguments which are a Writer w
and some other things, then the odds are pretty good that it's going to pass (some of) the other arguments to w.Write()
. So instead of fmt.Fprintf()
you could say "the return value flows to an argument of a call to any function, and the response body is any other syntactic argument of that call." You would have to test out how good this heuristic is, either by looking through the search results I linked earlier and seeing if you think it works most of the time, or by coding it up and running MRVA on it. (And you'd have to keep a case for w.Write()
as well.)
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.
Ah, I have just found out that there is a way to adapt the SimpleGlobal
approach to give you a source and a sink. I'll suggest it in a new review.
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 have added a way of using SimpleGlobal
with a source and a sink as two suggestions. Try it out. That will deal with the writer flowing to another function in the same project. It may also be looking for other external functions that the writer often goes to, like io.Writer.Write()
and fmt.Fprintf()
, and adding any that come up a lot in the relevant place.
exists(Method responseBodyWriter, DataFlow::CallNode writerCall | | ||
responseBodyWriter.hasQualifiedName(packagePath(), "Response", "BodyWriter") and | ||
call = responseBodyWriter.getACall() and | ||
writerCall = any(Method write | write.hasQualifiedName("io", "Writer", "Write")).getACall() and | ||
this = writerCall.getArgument(0) and | ||
DataFlow::localFlow(call.getResult(0), writerCall.getReceiver()) | ||
) | ||
or | ||
exists(Method responseBodyWriter, DataFlow::CallNode writerCall | | ||
responseBodyWriter.hasQualifiedName(packagePath(), "Response", "BodyWriter") and | ||
call = responseBodyWriter.getACall() and | ||
writerCall = any(Function fprintf | fprintf.hasQualifiedName("fmt", "Fprintf")).getACall() and | ||
this = writerCall.getSyntacticArgument(any(int i | i > 1)) and | ||
DataFlow::localFlow(call.getResult(0), writerCall.getArgument(0)) | ||
) |
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.
exists(Method responseBodyWriter, DataFlow::CallNode writerCall | | |
responseBodyWriter.hasQualifiedName(packagePath(), "Response", "BodyWriter") and | |
call = responseBodyWriter.getACall() and | |
writerCall = any(Method write | write.hasQualifiedName("io", "Writer", "Write")).getACall() and | |
this = writerCall.getArgument(0) and | |
DataFlow::localFlow(call.getResult(0), writerCall.getReceiver()) | |
) | |
or | |
exists(Method responseBodyWriter, DataFlow::CallNode writerCall | | |
responseBodyWriter.hasQualifiedName(packagePath(), "Response", "BodyWriter") and | |
call = responseBodyWriter.getACall() and | |
writerCall = any(Function fprintf | fprintf.hasQualifiedName("fmt", "Fprintf")).getACall() and | |
this = writerCall.getSyntacticArgument(any(int i | i > 1)) and | |
DataFlow::localFlow(call.getResult(0), writerCall.getArgument(0)) | |
) | |
exists(ResponseBodyWriterFlow::PathNode source, ResponseBodyWriterFlow::PathNode sink | | |
ResponseBodyWriterFlow::flowPath(source, sink) and | |
call = source.getNode() and | |
writerSinkAndBody(sink.getNode(), this) | |
) |
} | ||
|
||
private class ResponseBody extends Http::ResponseBody::Range { | ||
DataFlow::MethodCallNode call; |
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.
Please give call
a better name, which describes what it does. Better yet, replace this field with DataFlow::Node responseWriter
, set it to the response writer in the first place and return it in getResponseWriter
.
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 couldn't implement the better one.
) | ||
or | ||
exists(DataFlow::CallNode writerCall | | ||
writerCall = any(Function fprintf | fprintf.hasQualifiedName("fmt", "Fprintf")).getACall() and |
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.
writerCall = any(Function fprintf | fprintf.hasQualifiedName("fmt", "Fprintf")).getACall() and | |
writerCall = any(Function fprintf | fprintf.hasQualifiedName("fmt", ["Fprint", "Fprintf", "Fprintln"])).getACall() and |
May as well include all 3.
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.
Hi, I've also added some more sinks according to the search that you gave me before, please wait until I submit a new commit.
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've submitted the new sinks just now :)
Hi @owen-mc, Can I please add more content types for extending the XSS query scope in this PR? I want to increase the quality of this PR as much as possible because this PR is related to XSS too. |
go/ql/test/library-tests/semmle/go/frameworks/Fasthttp/fasthttp.go
Outdated
Show resolved
Hide resolved
I've asked the security lab if those changes are better done in this PR or separately. Apart from those, this PR is pretty much ready to merge. I just asked one question. |
If the things you want to add are related to fasthttp then it is best to include them in this PR. If the change to that regex is needed for modeling fasthttp then do it in this PR. If it's unrelated then you'd need to make a separate submission. |
go/ql/test/library-tests/semmle/go/frameworks/Fasthttp/fasthttp.go
Outdated
Show resolved
Hide resolved
I think it is related because I want to add two tests for the XSS sink with different content types. |
@owen-mc Is there anything left in this PR that I can help with? |
You said you wanted to "add more content types for extending the XSS query scope in this PR". Have you done that? |
Hi, I thought I should wait for the security lab response first. |
I should have been clearer. This comment is a summary of their response to me. |
To answer your question: this PR can be merged as is, or you can add a bit more to it first if you want. |
@owen-mc I think we can merge this PR as is because I'm going to add the additional content-types in another separate PR and I don't want to keep this PR open longer than this. |
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.
Great work!
@owen-mc Thank you so much for your support during this PR. I made many mistakes, but you handled it patiently and professionally. The good news is that I learned a lot and will add more frameworks to Codeql Golang this year (with fewer mistakes). |
Added fasthttp model.
Queries that this model supports contain SSRF, Access system file Queries like path traversal, All queries that are using UntrustedFlowSource, XSS-related queries that are using
SharedXss::Sink
, and queries that are usingHttp::Redirect
I directly extended the XSS Sink but as I understand now, it seems that I should work with
HTTP::ResponseBody
and somehow specify the content-type that is set for a related response. I couldn't implement this after many attempts in Codeql with my current knowledge.