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

sec: fix s3 and gcs host checks #512

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

dduzgun-security
Copy link
Collaborator

@dduzgun-security dduzgun-security commented Jan 7, 2025

Resolves potential incorrect host checks for S3 and GCS.

Ref: SECVULN-14562, SECVULN-14561, SECVULN-14564

Copy link
Contributor

@NodyHub NodyHub left a comment

Choose a reason for hiding this comment

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

Hey @dduzgun-security, great contribution. I have added added some comments and thoughts around the implementation, nothing special.

Looking forward to see this PR landing!

@@ -18,23 +21,39 @@ func (d *GCSDetector) Detect(src, _ string) (string, bool, error) {
return "", false, nil
}

if strings.Contains(src, "googleapis.com/") {
if strings.Contains(src, ".googleapis.com/") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we be sure that src is always only the FQDN and not also the path? So that we can be sure that src is not something like evil.foo.bar/hi-hashi.googleapis.com/whatever/. I will take a look into the call path, but just having that as a first thought in mind.

Copy link
Contributor

Choose a reason for hiding this comment

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

If take a look into the call graph, the mentioned case ^ might ocour:

go-getter/detect.go

Lines 47 to 60 in 842d6c3

func Detect(src string, pwd string, ds []Detector) (string, error) {
getForce, getSrc := getForcedGetter(src)
// Separate out the subdir if there is one, we don't pass that to detect
getSrc, subDir := SourceDirSubdir(getSrc)
u, err := url.Parse(getSrc)
if err == nil && u.Scheme != "" {
// Valid URL
return src, nil
}
for _, d := range ds {
result, ok, err := d.Detect(getSrc, pwd)

Might be worse to parse the url and inspect only the host. :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, what about various cases? 🙈

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 see. You create your own url here later on:

url, err := url.Parse(fmt.Sprintf("https://www.googleapis.com/storage/%s/%s/%s",

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch, it's still not a bad idea to url parse it here too and validate the sufix

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, well ... as long as you check or for the suffix of the host name :)

}

// Rule 6: Bucket name cannot contain "google" or common misspellings like "g00gle"
googlePattern := `google|g00gle`
Copy link
Contributor

Choose a reason for hiding this comment

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

What about go0gle? Is there somewhere a spec to that?

Copy link
Contributor

@picatz picatz Jan 8, 2025

Choose a reason for hiding this comment

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

I wonder if there will be too many permutations to consider here. The documentation really only says:

Bucket names cannot contain "google" or close misspellings, such as "g00gle".
https://cloud.google.com/storage/docs/buckets#naming

We could go further with a regex like g[o0]{2}gle, but there's also 1s for l, etc. It's a rabbit hole I'm not sure we want to go down. 🕳️ 🐇

Since this will be enforced by Google Cloud when creating a bucket, maybe we don't really need to worry about it for the means of detecting a bucket. If we send an invalid bucket name in a request to GCS, it should just fail.

I suspect we can simply detect it likely being a bucket without checking these common misspellings.

Copy link
Contributor

@NodyHub NodyHub Jan 8, 2025

Choose a reason for hiding this comment

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

💯
I don't mind if we leave it as @dduzgun-security proposed it and any further validation is done on the server site endpoint. My comment from above was mainly a thing that came through my mind while reading that check.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

After talking with @picatz, we won't need this part as it might add more risk or blockers on specific use cases by people. Also, this doesn't allow any injection point and Google validates the call.

}

// Rule 2: Bucket name cannot start or end with a hyphen, dot, or underscore
if bucket[0] == '-' || bucket[0] == '.' || bucket[len(bucket)-1] == '-' || bucket[len(bucket)-1] == '.' || bucket[len(bucket)-1] == '_' {
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
if bucket[0] == '-' || bucket[0] == '.' || bucket[len(bucket)-1] == '-' || bucket[len(bucket)-1] == '.' || bucket[len(bucket)-1] == '_' {
if bucket[0] == '-' || bucket[0] == '.' || bucket[0] == '_' || bucket[len(bucket)-1] == '-' || bucket[len(bucket)-1] == '.' || bucket[len(bucket)-1] == '_' {

}

// Rule 4: Bucket name cannot be an IP address (only digits and dots, e.g., 192.168.5.4)
ipPattern := `^(\d{1,3}\.){3}\d{1,3}$`
Copy link
Contributor

Choose a reason for hiding this comment

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

999.999.999 is not a valid IP address, but is it still officially invalid?

Choose a reason for hiding this comment

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

Adding on to this maybe a more direct check should be used for example:

// IPv4 regex matches 0.0.0.0 to 255.255.255.255
simpleRegex := `^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`

Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 Maybe it would be even better to avoid a regex, and use the standard library:

https://pkg.go.dev/net/netip#ParseAddr

addr, err := netip.ParseAddr(ipString)
...

Compared to the net.IP type, Addr type takes less memory, is immutable, and is comparable (supports == and being a map key).

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, is it not a bit too much over-processing for a path-validation to try to convert parts of the path into a Addr object? I don't have a perfect solution in mind 🤷

return false
}

// Rule 3: Object names cannot be exactly '.' or '..'
Copy link
Contributor

Choose a reason for hiding this comment

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

Cant be anyway, due to the fact that you perform the path.Clean(..) at the beginning 😬 however, the check is not wrong :)

@@ -233,3 +233,35 @@ func TestGCSGetter_GetFile_OAuthAccessToken(t *testing.T) {
}
assertContents(t, dst, "# Main\n")
}

func Test_GCSGetter_ParseUrl_Malformed(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be great to have a positive example for this test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants