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

Integrate #114 to improve logon with 2FA #214

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

infamousjoeg
Copy link
Owner

This should fix 403 issues when attempting a challenge/response via MFA on self-hosted PAS.

@infamousjoeg infamousjoeg added bug Something isn't working customer This issue submitted by customer labels Nov 8, 2023
@infamousjoeg infamousjoeg self-assigned this Nov 8, 2023
@infamousjoeg
Copy link
Owner Author

@Infraded,

Can you please build and test this locally to see if it fixes the problems you're experiencing?

make build from the root directory will build the executable into bin.

@Infraded
Copy link

Infraded commented Nov 8, 2023

Tested and got a failure after password input.

cybr-cli % ./bin/cybr logon -u $USER -a radius -b https://cyberark.internal.domain
Enter password:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x8 pc=0x100a92f38]

goroutine 1 [running]:
github.com/infamousjoeg/cybr-cli/cmd.logonToPAS({{0x16f77f52b, 0x23}, {0x16f77f521, 0x6}, {0x0, 0x0}, 0x0, {0x0, 0x0}, {0x0, ...}}, ...)
	/Users/infraded/dev/cybr-cli/cmd/logon.go:81 +0x148
github.com/infamousjoeg/cybr-cli/cmd.glob..func40(0x140001dc200?, {0x100a9a7a6?, 0x4?, 0x100a9a7aa?})
	/Users/infraded/dev/cybr-cli/cmd/logon.go:174 +0x4b8
github.com/spf13/cobra.(*Command).execute(0x100f7cec0, {0x1400012f500, 0x6, 0x6})
	/Users/infraded/go/pkg/mod/github.com/spf13/cobra@v1.1.1/command.go:854 +0x53c
github.com/spf13/cobra.(*Command).ExecuteC(0x100f7d6a0)
	/Users/infraded/go/pkg/mod/github.com/spf13/cobra@v1.1.1/command.go:958 +0x310
github.com/spf13/cobra.(*Command).Execute(...)
	/Users/infraded/go/pkg/mod/github.com/spf13/cobra@v1.1.1/command.go:895
github.com/infamousjoeg/cybr-cli/cmd.Execute()
	/Users/infraded/dev/cybr-cli/cmd/root.go:31 +0x24
main.main()
	/Users/infraded/dev/cybr-cli/main.go:6 +0x1c```

@infamousjoeg
Copy link
Owner Author

❯ cybr logon -b https://pineapple.privilegecloud.cyberark.cloud -a cyberark -u jgarcia --verbose
Enter password:

Successfully logged onto PAS as user jgarcia.

Okay, I fixed the nil pointer dereference. Please give it another shot, @Infraded.

@Infraded
Copy link

Infraded commented Nov 8, 2023

No more nil pointer dereference, but getting this now after password:

2023/11/08 10:55:50 Failed to logon to the PVWA and error details are unavailable. Unable to unmarshal response from PAS REST API Web Service. unexpected end of JSON input

On the older builds I was getting the "expected" 500 responses and I've confirmed that I can get through via the API directly

@Infraded
Copy link

Infraded commented Nov 8, 2023

Added some debug outputs and it seems like here: https://github.com/infamousjoeg/cybr-cli/pull/214/files#diff-34ff7a443be24db6bbf225c6913a8c0f097b382c1f5dc6f6de688935e09e5a88R173-R175 is where it dies. It gets a 500 response back for ErrorCode ITATS542I ( which IS an improper HTTP response for this result, but that's what we get ) that it's considering an error in getResponse and bombing out downstream

@infamousjoeg
Copy link
Owner Author

Thanks @Infraded. I apologize that I don't have a radius server setup to test this on. I've implemented a conditional in an attempt to handle status code 500 and the existence of ITATS542I. I'm hoping this is the ticket:

// Check if the response status code is 500 and look for the error message ITATS542I
if res.StatusCode == 500 && bytes.Contains(content, []byte("ITATS542I")) {
newCtx := context.WithValue(ctx, contextKeyCookies, res.Cookies())
return newCtx, content, err
} else if res.StatusCode >= 300 {
return ctx, nil, fmt.Errorf("Received non-200 status code '%d'", res.StatusCode)
}

@Infraded
Copy link

Infraded commented Nov 8, 2023

Partial success, with some more tweaking.

if err != nil {
return ctx, nil, err
}
defer res.Body.Close()
is still catching any error from getResponse which looks to be doing the same checking sendRequestRaw was doing
if res.StatusCode >= 300 {
return *res, fmt.Errorf("Received non-200 status code '%d'", res.StatusCode)
}

When I bypass that first err check in sendRequestRaw it proceeds properly into the the ITATS542I body conditional but does not further prompt me for the next inputs and returns Successfully logged onto PAS ...

@infamousjoeg
Copy link
Owner Author

@Infraded,

I want to avoid removing that error check that you bypassed. The 500 status code shouldn't be causing it to error there. That should just be for network and protocol errors of the net/http Do method.

What I did find, however, is in the logic of cmd/logon.go a conditional that wasn't checking for the error code to prompt for OTP. So, I made changes to that so that it prompts for OTP when the code is detected.

If you need to, please manually bypass the SendRawRequest first error check if you need to... I'm going to have to figure something out for that. @AndrewCopeland: Do you have any ideas, by chance?

@Infraded
Copy link

Infraded commented Nov 9, 2023

I wouldn't want to remove that check either, but getResponse has the same >300 = error logic that was patched in sendRequestRaw so it's returning an err that either needs another conditional filtering to be handled differently in getResponse or to also be filtered in this check . It actually looks like that status code checking logic is duplicated, maybe it moved or got altered between the original PR time and now. Another issue I found yesterday was the actual return of content from that call. auth.go is expecting data passed down from getResponse on both content/response and err, but err from the getResponse call is getting clobbered by the io.ReadAll bit before being returned in sendRequestRaw.

I'm hacking through it locally, but am far from proficient in Go. Bypassing the error catch still and making sure sendRequestRaw returns the err from getResponse, I can get a second prompt now, which for me is still and intermediate 2fa type choice, but it fails when sending that call back up with 403, indicating maybe the cookies/context are not being kept.

@infamousjoeg
Copy link
Owner Author

@Infraded I got it! I got my hands on a self-hosted PAM lab with Radius configured. I needed to implement AND/OR as needed for when ITATS542I was contained in the response body. This should be prompting for one-time passcode now:

image

The problem now is handling the OTP code afterwards. Troubleshooting that portion now... PROGRESS!

@infamousjoeg
Copy link
Owner Author

I am pretty sure at this point after all my testing that challenge/response via API is not supported. I have tested using Postman, curl, and writing a small Go app just to attempt authentication with a cookie jar. All tests failed with the error: {"ErrorCode":"PASWS013E","ErrorMessage":"Authentication failure for User [kyle]."}

Even the Vault trace logs show an issue with the session handling in the way it flips between 0 and -1 for session IDs:
image

Here's what the Vault trace logs looks like with a successful Radius challenge/response authentication from the PVWA:
image

Further, I used Fiddler to collect all request/response data of a successful authentication from the Browser via PVWA and mimicked the same actions in my Go app to which I was still met with failure.

I'm going to keep this open for now in hopes that someone can prove that they are able to do challenge/response radius via API, but from what I understand from the Discord community... many have attempted, all have failed.

@infamousjoeg infamousjoeg added help wanted Extra attention is needed backlog Not on foreseeable roadmap question Further information is requested labels Nov 14, 2023
@infamousjoeg
Copy link
Owner Author

Used gorilla/sessions instead of net/http and it is working using that. I don't know that I want to switch everything over to using gorilla/sessions, though. Going to continue to investigate and use that as a last resort.

@Infraded
Copy link

I was definitely able to get a token back from the API manually with challenge/response. Our usage has two sets of challenges and even that was OK when manually hitting with Insomnia. Sounds like you had some success though, is it in a state to test or still working on it?

* Update ci.yml for Pineapple

* Fix indentation error

* Revert to previous checkout/install

* Add /api to CC URL

* move to ubuntu-latest hosted runner

* switch from ldap to identity

* Change test account to cyberark

* Change PAS_ADDRESS to PAS_HOSTNAME

* removed sleep

* Update IDs for SaaS

* accountSafeName _ to -

* Create new test account

* Add Content-Length to request header

* Add empty struct for body on POST

* Add emptyBody to logoff

* Change ListSafeMembers.Members.value.MemberId to interface{}

* Update RemoveSafeMember from v1 to v2 API

* Add MemberType to Add Safe Member test

* Removed early DeleteSafe

* Add emptyBody to UnsuspendUser

* Add Create Temp PEM Files step

* Update CCP variable paths

* Base64 decode CCP Certs

* Add CCP_HOSTNAME env var

* Move test workflow to self-hosted runner

* Update CCP_CLIENT_PRIVATE_KEY
Copy link

Quality Gate Passed Quality Gate passed

The SonarCloud Quality Gate passed, but some issues were introduced.

3 New issues
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarCloud

@Infraded
Copy link

@infamousjoeg Any further thoughts on this? Is the current state of this branch worth testing or is there more to do still?

@infamousjoeg
Copy link
Owner Author

@Infraded there's something with the way Go needs to handle the cookies when dealing with the challenge/response that I just cannot figure out. PowerShell does it natively, so it's not something that anyone with experience there has encountered before. I did go to the Discord Community in hopes that a customer or partner may have ran into this in the past and they had. Unfortunately, no one had any answers or workarounds outside of "Use PowerShell".

This is still on my radar, but it's going to take time for me to learn what is going on. Any community contributions in the meantime are welcomed!

@Infraded
Copy link

From my testing, it just needs to keep cookies during the challenge/response calls, until it gets the token. And also not have any old ones when it starts that process. I've got a script now just using a cookie jar w/ curl that works ok. I don't know the mechanisms for that in Go (yet), but will keep poking around too. Appreciate your work on this!

@infamousjoeg infamousjoeg changed the base branch from v1.0.2-release to main January 24, 2024 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backlog Not on foreseeable roadmap bug Something isn't working customer This issue submitted by customer help wanted Extra attention is needed question Further information is requested
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants