Exploiting Hardcoded Keys to achieve RCE in Yellowfin BI
Introduction
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.
Exploring the Pre Auth Attack Surface
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.
Bypassing the Authentication
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<T> extends AbstractAction<T>
{
...
@Override
public T execute(final YFActionMapper<T> 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(