At Assetnote, we often audit enterprise software source code to discover pre-authentication vulnerabilities. Yellowfin BI had significance to us because it is a popular analytics platform for product managers, and we were able to deliver value to customers of our Attack Surface Management platform by alerting our customers about their exposure.
One of the patterns that we often come across when performing source code review is the usage of hardcoded keys. This blog post will describe how we leveraged a number of hardcoded keys inside a Java monolith application (Yellowfin BI) to achieve command execution. All three of our authentication bypass vulnerabilities were due to hardcoded keys which were used to encrypt/decrypt auth related data.
This blog post will walk you through the entire exploit chain which goes from pre-authentication to post-authentication, leading finally to command execution. In this blog post, we will provide exploit code for each vulnerability.
The vulnerabilities in this blog post were discovered by Max Garrett, who is a security researcher at Assetnote.
You can find all of our exploit code for Yellowfin BI in our GitHub repository: https://github.com/assetnote/exploits/tree/main/yellowfin-bi/.
CVE’s have been claimed for these issues but not yet assigned.
When approaching large Java monolith codebases, it is important that you map out the pre-authentication attack surface in as much detail as possible. This requires you to understand all of the routes, both static and dynamic, and then determine which portion of these routes are actually accessible without any authentication.
After a mapping of the pre-authentication routes has been made, whether mentally or written down in your notes, we then need to determine how user input is processed by these routes and understand which routes take in what user input. In this process you can often find areas of concern or interest, simply based off the names of the controllers or parameters.
For the case of Yellowfin BI, while there were a number of routes that were pre-authentication, in the time that we had to audit the software, we did not discover anything that led to significant security impact. However, in the process of auditing the code, we did notice that there seemed to be a number of hardcoded keys being used for various authentication related functionalities which made us think that we could likely perform authentication bypass attacks.
When you cannot exploit something on the pre-authentication attack surface, it’s time to switch gears and understand how we can potentially bypass authentication and target the rich post-authentication attack surface.
We found that the application had a specific pattern which indicated when authentication was successful, and by searching for this pattern we were able to identify all the places that implement authentication/login logic:
session.setAttribute("SessionData",BEAN)
While it wasn’t the case in Yellowfin BI, another common mechanism to enforce authentication can be through routing. This is also something that should be checked thoroughly as in many cases, it is possible to bypass authentication through the manipulation of routing mechanisms inside an application.
CVE-2022-47884: Authentication Bypass via StoryBoardAction
When auditing the authentication flows found in the source code, we discovered com/hof/mi/web/action/StoryBodyAction.java
which contained some logic where StoryBoardAction
allowed us to sign in as any user, as long as a signature check was passed. Due to a hardcoded private key being used, anyone can pass the signature check.
The source code for this controller can be found below:
//com/hof/mi/web/action/StoryBodyAction.java
public class StoryBodyAction extends AbstractAction
{
...
@Override
public T execute(final YFActionMapper yfActionMapper, final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) throws IOException, ServletException {
final HttpSession session = httpServletRequest.getSession();
StoryBodyAction.u01ca.info((Object)" StoryBodyAction entered");
final String parameter = httpServletRequest.getParameter("s");
final String parameter2 = httpServletRequest.getParameter("ts");
final Integer ipPerson = UtilInteger.TryParse(httpServletRequest.getParameter("ipPerson"));
final Integer ipOrg = UtilInteger.TryParse(httpServletRequest.getParameter("ipOrg"));
String parameter3 = (String)httpServletRequest.getAttribute("STORYUUID");
...
if (!this.checkSig(parameter3, parameter2, parameter)) {
return yfActionMapper.findForward("failure");
....
DBAction dbAction = null;
try {
dbAction = new DBAction();
final SessionBean sessionBean = RESTUserCache.getInstance().getSessionBean(dbAction, ipPerson, ipOrg);
sessionBean.setLoggedOn();
sessionBean.setBrowserInfo(new BrowserInfo(httpServletRequest));
session.setAttribute("SessionData", (Object)sessionBean);
..........
}
private boolean checkSig(final String str, final String str2, final String src) {
boolean verify = false;
try {
final PublicKey x509PublicKey = new SecurityKeyUtil().parseX509PublicKey("RSA", "...");
final StringBuilder sb = new StringBuilder(str);
sb.append('n').append(str2);
final byte[] bytes = sb.toString().getBytes(StandardCharsets.UTF_8);
final byte[] decode = Base64.getDecoder().decode(src);
final Signature instance = Signature.getInstance("SHA512withRSA");
instance.initVerify(x509PublicKey);
instance.update(bytes);
verify = instance.verify(decode);
}
catch (Exception ex) {
...
}
return verify;
}
}
The above code is responsible for generating a session for any user, given that we can pass the signature checks required. The signature is generated by the code below, which contains the hardcoded RSA private key. You can see where it was hardcoded by finding the comment // #1:
, for readability purposes we have removed the private key from the code below:
...
//bi/yellowfin/api/rest/service/StoriesApiService.java
@RESTVersionRange(from = RESTAPIVersion.V1, to = RESTAPIVersion.CURRENT)
@GetMapping(value = { "/stories/{storyUuid}/body" }, produces = { "text/*+html", "application/*+json" })
public Object getStoryBody(...) {
...
try {
// #1: hardcoded RSA private key
final PrivateKey generatePrivate = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode("SENSITIVE HARDCODED PRIV KEY")));
final StringBuilder sb = new StringBuilder(uuid.toString());
sb.append('n').append(currentDateTime);
final byte[] bytes = sb.toString().getBytes(StandardCharsets.UTF_8);
final Signature instance = Signature.getInstance("SHA512withRSA");
instance.initSign(generatePrivate);
instance.update(bytes);
encode = URLEncoder.encode(new String(Base64.getEncoder().encode(instance.sign())), "UTF-8");
}
...
final SessionBean sessionBean = this.getSessionBean();
final StringBuilder sb2 = new StringBuilder("redirect:/StoryBody.i4?storyUUID=");
sb2.append(uuid.toString());
sb2.append("&s=").append(encode);
sb2.append("&ts=").append(currentDateTime);
sb2.append("&ipPerson=").append(sessionBean.getPrsnBean().getIpPerson());
sb2.append("&ipOrg=").append(sessionBean.getPersonSearchIpOrg());
...
}
You will notice that the first snippet of code contains this like:
session.setAttribute("SessionData", (Object)sessionBean);
This was the pattern that we were looking for across the codebase and when specifically looking at this controller, we realised that it was vulnerable to authentication bypass.
The exploit code for the vulnerability above can be found here.
CVE-2022-47885: Authentication Bypass via JsAPI
While auditing the code, we also identified another authentication bypass. This was found in the JsAPI Servlet, where it was possible to authenticate as a user through the EXTAPI-IPID cookie, it was found that this cookie is just an AES encrypted using hardcoded keys user id. So It is possible for anyone who knows the victim’s user id to create a valid session as their account.
Due to the similarities to YFN_1, we wont be going into detail about the code analysis, however if you’re curious you can take a look at the following files in a vulnerable version of Yellowfin: com/hof/mi/jsapi/JsAPI2AuthHandler.java
& com/hof/util/CryptoHelperSimple.java
.
The exploit code for the vulnerability above can be found here.
CVE-2022-47882: Privilege Escalation via REST API
In addition to the authentication bypass vulnerabilities we discovered, we found that Yellowfin’s implementation of JWTs inside their REST API relied on a hardcoded key (JWT secret). This allowed anyone with a valid refresh token id and the extracted hardcoded key to create a valid JWT as any user.
This vulnerability is limited to privilege escalation only, as it requires a valid refresh token ID which can only be generated by a successful login via /api/refresh-tokens
.
While we will not be discussing the underlying code for this vulnerability, you can find the affected components in bi/yellowfin/api/rest/auth/token/process/JWTProcess.java
and bi/yellowfin/api/rest/auth/AuthenticationInterceptor.java
.
An exploit for this vulnerability can be found here.
In most enterprise applications, the post-authentication attack surface can be quite rich and consequently the extra functionalities can often lead to command execution.
From our experience, we have often found that after bypassing authentication, we are able to access features that interact with the file system or network in potentially dangerous ways (arbitrary file uploads, server-side request forgery).
In some cases, enterprise applications allow you to modify or connect to datasources. This was the case in Yellowfin BI, which makes sense because it is a business intelligence software that intentionally needs to connect to arbitrary datasources to pull data into the application.
Whenever we discover a mechanism to connect to arbitrary datasources that we control, the first thing we investigate is whether or not we can achieve command execution through techniques such as JNDI injection or JDBC injection.
For Yellowfin BI, we were able to execute arbitrary commands through JNDI injection. We used a forceString
gadget to achieve command execution. You can read more about JNDI injections in this blog post.
The full proof-of-concept, including the authentication bypass leading to RCE can be found here.
In order to remediate these vulnerabilities, upgrade to Yellowfin BI 9.8.1. You can see the release notes for this version of Yellowfin BI at the following link.
Authentication bypass vulnerabilities can often be found by strategically looking at source code that sets sessions. As demonstrated in this blog post, we found a number of authentication and authorization related issues in Yellowfin BI.
By exploiting these issues, we were able to access a rich attack surface post-authentication that allowed us to exploit the application further, ultimately achieving remote command execution.
We hope that by explaining our thought process that led to these findings, you can replicate these strategies when auditing software for security vulnerabilities.