infamousjoeg / cybr-cli

A "Swiss Army Knife" command-line interface (CLI) for easy human and non-human interaction with @CyberArk suite of products.
Apache License 2.0
71 stars 15 forks source link

Integrate #114 to improve logon with 2FA #214

Open infamousjoeg opened 10 months ago

infamousjoeg commented 10 months ago

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

infamousjoeg commented 10 months ago

@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 commented 10 months ago

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 commented 10 months ago
❯ 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 commented 10 months ago

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 commented 10 months ago

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 commented 10 months ago

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: https://github.com/infamousjoeg/cybr-cli/blob/8e7635aee3461761f43510ff54354419cc9e1ed2/pkg/cybr/helpers/httpjson/httpjson.go#L183-L189

Infraded commented 10 months ago

Partial success, with some more tweaking. https://github.com/infamousjoeg/cybr-cli/blob/8e7635aee3461761f43510ff54354419cc9e1ed2/pkg/cybr/helpers/httpjson/httpjson.go#L173-L176 is still catching any error from getResponse which looks to be doing the same checking sendRequestRaw was doing https://github.com/infamousjoeg/cybr-cli/blob/8e7635aee3461761f43510ff54354419cc9e1ed2/pkg/cybr/helpers/httpjson/httpjson.go#L135-L137

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 commented 10 months ago

@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 commented 10 months ago

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 commented 10 months ago

@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 commented 10 months ago

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 commented 10 months ago

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 commented 10 months ago

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?

sonarcloud[bot] commented 9 months ago

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 commented 9 months ago

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

infamousjoeg commented 8 months ago

@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 commented 8 months ago

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!