Broken access control in GoAnywhere Admin portal
Table of content
- TL;DR
- Spot the bug
- First exploit attempt: accessing Dashboard and identifying some obstacles
- Second exploit attempt: Found some endpoint to exploit, create a high-privilege admin account from a low-privilege admin account
- Final exploit attempt: Preauth bypass access control, create a new high-privilege admin account
- Bonus
- Timeline (GMT +7)
0. TL;DR
With the authentication bugs, I can perform several actions like: creating a high-privileged admin account from a low-privileged admin account, accessing features that an admin account doesn't have access to, etc. But the most critical one (which also happens to be the easiest) is creating an admin account pre-authenticated:
- Class SecurityFilter control access by comparing relative URIs with exact path → use
/a/../
to bypass - SecurityFilter will block access to the first-time setup page if the relative URI path equals
/wizard/InitialAccountSetup.xhtml
and admin is set up → access using/a/../wizard/InitialAccountSetup.xhtml
(violate the first condition) - Register a new admin account & profit
1. Spot the bug
After some time getting acquainted with the flow of the program, I realized that most of the authentication checks are in com.linoma.dpa.security.SecurityFilter.doFilter
This method has few if-conditions to determine if you can immediately pass this filter. I then proceed to dig into those conditions to see if there is anything interesting
One part of the code caught my attention
this.map
value is loaded from gamft-7.4.0.jar/com/linoma/dpa/iam/config/security-config.xml
→ the access control check here is just a blacklist URI check
Therefore, I can easily bypass the whole filter with path traversal /a/../
2. First exploit attempt: accessing the Dashboard and identifying some obstacles
There are a few challenges when exploiting the server.
GoAnywhere uses the JSF framework, meaning the content of the page will be served in a “state”. Basically, I need to do a GET request first to initialize the state, and then I can send POSTs to edit that state
However, when access /goanywhere/a/../Dashboard.xhtml
(or most of the pages), I receive a 500 error. This is because when accessing a page normally, JSF will render all components, including those that require user preferences. But since we’re not logged in yet, .getUserPreferences()
will return null
→ Null Pointer Exception [1]
After reading and analyzing the code a bit more carefully, I realized that I could use the header Faces-Request: partial/ajax
to not render all components (which only much later that I know this is a JSF framework header) and only get the state ID
Voila, I now have the state of LoginSettings
page
There are also a few other problems:
- Some of the buttons can only be rendered if you have the authorization
- Some of the web functions require the page to be fully rendered with a POST request to get a new
viewstate
(no can do, because rendering the page fully will cause the 500 Internal error mentioned above [1])
- When saving some system settings, the server requires a user session
- When creating a new form to update/add new stuff, we (most of the time) need to initialize the form from a button on the previous page. If we access the form page directly, some forms will be empty because the form object needs to be inflated with some data when initializing
Example:
The form class corresponding with this page: com.linoma.ga.ui.admin.users.EditUserForm
Method to insert data into fields (in other words, "method to initialize the form"): com.linoma.ga.ui.admin.users.EditUserForm#setUserBE
. The method is called in 3 different places:
Let’s see com.linoma.ga.ui.admin.users.ListUsersForm#editUser
editUser
action is used in ListUsers.xhtml
file
which is this edit button
Given the problems I faced, obviously, the impact is very limited with most of the endpoints
3. Second exploit attempt: Found some endpoint to exploit, create a high-privilege admin account from a low-privilege admin account
After a period of time, I finally found some ways for my exploit to actually have impacts on the server, such as:
- Modify none-system settings (
/godrive/GoDriveGlobalSettings.xhtml
/security/loginmethods/LoginSettings.xhtml
, etc.) - Use some JSF param to read settings/data from the server
We cannot fully render the page (because of the preferences stuff mentioned above), so how about partially rendering it?
It turns out I can use the param javax.faces.partial.render=id
(on both POST and GET requests) to render only the required component. Therefore, other unnecessary components (such as component needs session.preferences
) will not be rendered → no 500 Internal Server Error
- Exploit some form used to create stuff (because such form may not need to be initialized)
- Use a low-privilege admin account to completely avoid any problems regarding the session (mentioned at [1]) 🤡
Impact: I can create a new full-role admin account from a zero-role admin account (need an admin account because, without one, the session thing will cause a Null Pointer Exception [1])
→ a post-auth privilege escalation bug, leads to the entire server compromised
4. Final exploit attempt: Preauth bypass access control, create a new high-privilege admin account
Still not satisfied with a post-auth bug, I tried harder to find other bugs to chain with to get a pre-auth RCE / Bypass access
After a long time, I realized that there’s an endpoint I completely forgot: /wizard/InitialAccountSetup.xhtml
This path only appears once, after you input the key to activate the server and start setting up the server
With this endpoint, I can easily add a new full-role admin account because the code that checks whether the server is already set up or not also lies in SecurityFilter
→ Pre auth access bypass leads to the creation of high-privilege admin
5. Bonus
com.linoma.ga.ui.wc.application.WebClientSecurityFilter
use startWith()
to control access → also can be bypassed using ../
(credit to @q5ca)
6. Timeline
- Sep 14 2023: Found the Post-auth privilege escalation bug
- Oct 05 2023: Report to info@helpsystems.com
- Oct 07 2023: Report to goanywhere.support@fortra.com
- Oct 09 and Oct 10 2023: goanywhere.support@fortra.com reply and ask for CVE number. I explain that this is a new vulnerability and its impacts
- Oct 12 2023: Found the Pre-auth access bypass bug
- Oct 13 2023: Since this is a pre-auth bug, I quickly remind them about my report
- Oct 25 2023: GoAnywhere (wrongly) closes my report case
- Dec 04 2023: GoAnywhere published a security advisory about the bug
(Image from https://twitter.com/malcolmx0x/status/1732126084584620515)
- Jan 22 2024: CVE-2024-0204 is published to address the preauth bug. Link: Advisory FI-2024-001 (fortra.com)
- Jan 27 2025: Disclose my finding security.reports@fortra.com
- Mar 14 2024: CVE-2024-25156 is published to address the path traversal bug. Link: Advisory FI-2024-004 (fortra.com)