Skip to content
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

Merged
merged 78 commits into from
Jan 14, 2024
Merged

Go: fasthttp #14123

merged 78 commits into from
Jan 14, 2024

Conversation

am0o0
Copy link
Contributor

@am0o0 am0o0 commented Sep 2, 2023

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 using Http::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.

@github-actions github-actions bot added the Go label Sep 2, 2023
@ghsecuritylab ghsecuritylab marked this pull request as ready for review September 15, 2023 10:02
@ghsecuritylab ghsecuritylab requested a review from a team as a code owner September 15, 2023 10:02
@smowton smowton requested review from mbg and owen-mc September 15, 2023 11:54
Copy link

@github-advanced-security github-advanced-security bot left a 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.

@mbg
Copy link
Member

mbg commented Sep 18, 2023

Hi @amammad! Thank you so much for putting together models for the fasthttp library 🙏🏻

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?

@am0o0
Copy link
Contributor Author

am0o0 commented Sep 18, 2023

@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.

@am0o0
Copy link
Contributor Author

am0o0 commented Sep 18, 2023

@mbg tests are ok on my side I don't have any idea why it failed here.

@mbg
Copy link
Member

mbg commented Sep 18, 2023

@amammad can you try rebasing your branch on main? It looks like the test failure is a result of us upgrading to Go 1.21 in CI in #13867 which also updated the expected test output for Go 1.21.

@am0o0
Copy link
Contributor Author

am0o0 commented Sep 18, 2023

@mbg I think it is OK now.

Copy link
Member

@mbg mbg left a 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/semmle/go/frameworks/Fasthttp.qll Fixed Show resolved Hide resolved
go/ql/lib/semmle/go/frameworks/Fasthttp.qll Fixed Show fixed Hide fixed
go/ql/lib/semmle/go/frameworks/Fasthttp.qll Fixed Show resolved Hide resolved
go/ql/lib/semmle/go/frameworks/Fasthttp.qll Fixed Show fixed Hide fixed
Comment on lines 98 to 119
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

The field call is only used in one side of disjunct.
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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().

Copy link
Contributor Author

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 :)

Copy link
Contributor

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.)

Copy link
Contributor

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.

Copy link
Contributor

@owen-mc owen-mc left a 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.

go/ql/lib/semmle/go/frameworks/Fasthttp.qll Show resolved Hide resolved
Comment on lines 102 to 116
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))
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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;
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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 :)

@am0o0
Copy link
Contributor Author

am0o0 commented Dec 14, 2023

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.
according to this research I can extend this regex.

@owen-mc
Copy link
Contributor

owen-mc commented Dec 14, 2023

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.

@owen-mc
Copy link
Contributor

owen-mc commented Dec 14, 2023

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.

@am0o0
Copy link
Contributor Author

am0o0 commented Dec 14, 2023

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.

I think it is related because I want to add two tests for the XSS sink with different content types.

@am0o0
Copy link
Contributor Author

am0o0 commented Dec 23, 2023

@owen-mc Is there anything left in this PR that I can help with?

@owen-mc
Copy link
Contributor

owen-mc commented Jan 2, 2024

You said you wanted to "add more content types for extending the XSS query scope in this PR". Have you done that?

@am0o0
Copy link
Contributor Author

am0o0 commented Jan 2, 2024

Hi, I thought I should wait for the security lab response first.

@owen-mc
Copy link
Contributor

owen-mc commented Jan 2, 2024

I should have been clearer. This comment is a summary of their response to me.

@owen-mc
Copy link
Contributor

owen-mc commented Jan 4, 2024

To answer your question: this PR can be merged as is, or you can add a bit more to it first if you want.

@am0o0
Copy link
Contributor Author

am0o0 commented Jan 14, 2024

@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.

Copy link
Contributor

@owen-mc owen-mc left a 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 owen-mc merged commit 057ee85 into github:main Jan 14, 2024
14 checks passed
@am0o0
Copy link
Contributor Author

am0o0 commented Jan 14, 2024

@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).

@am0o0 am0o0 deleted the amammad-go-fastHttp branch September 14, 2024 11:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants