背景
ZenPhoto 是一款小巧的相册软件,具备 RSS 输出、FTP 上传方式、Tag、评论回复等功能。基于 PHP+MySQL 环境,它的主旨是简单,这也就是说不管是用户还是网站管理员,都可以很容易的使用它。
关于二阶注入,上一篇文章有写过Second Order SQL Injection
漏洞复现
访问
http://localhost:8888/zenphoto-zenphoto-1.4.8/zp-core/admin-options.php?saved&tab=gallery
找到Sort gallery by
字段,设置为Custom
,并在custom filed
字段后面输入id,extractvalue(0x0a,concat(0x0a,(select version())))%23
,最后点Apply
访问
http://localhost:8888/zenphoto-zenphoto-1.4.8/zp-core/admin-upload.php?page=upload&tab=http&type=images
如果服务端开启了报错,那么就能在这个页面找到报错消息。当然一般线上服务我们都会关闭掉报错,这也没关系,继续访问
http://localhost:8888/zenphoto-zenphoto-1.4.8/zp-core/admin-logs.php?page=logs
就能看到报错日志了,其中就抛出了数据库版本号
审计
那么在点Apply
的时候,发生了什么呢?我拦下了数据包
|
|
定位到/zenphoto-zenphoto-1.4.8/zp-core/admin-options.php
文件,找到savegalleryoptions
相关代码
|
|
定位到gallery_sorttype
参数, $st = strtolower(sanitize($_POST['gallery_sorttype'], 3));
,留意到这里对$_POST
进来的值传到了sanitize
函数。该函数的在文件/zenphoto-zenphoto-1.4.8/zp-core/functions-common.php
|
|
这个函数对传入$input_string
先判断是否是数组,如果是,就遍历数组,过滤每个值。如果不是,就直接将$input_string
丢入sanitize_string
函数去做过滤,最后返回过滤后的值。其实这里有点不严谨,因为如果传入是数组的话,仅仅对value
做了过滤,而key
是原样保留的,会有安全隐患。我简单改造了下这个函数
|
|
继续在该文件里跟踪sanitize_string
函数
|
|
这个函数在服务器开启gpc
的情况下,会先对参数stripslashes
一下,sanitize_level
为3
的情况下,将$input
传入了getBare
函数,并返回该函数执行结果。跟进getBare
函数
|
|
可以看到仅仅是替换了一些html标签,并将HTML实体转换为字符,然后就返回内容了,看来做得并不多。
回到savegalleryoptions
代码块
|
|
strtolower
函数将输入的内容都转换为了小写,$_POST[‘customalbumsort’]
的内容也经过了sanitize
函数的过滤,最后就调用了_zp_galleryd
的setSortType
方法,继续跟进,找到_zp_gallery
实例
$_zp_gallery
即为Gallery
类的一个实例,定义了一些protected
变量
可以看到setSortType
函数直接把参数传递给了set
函数,在文件中找到set
函数的定义
|
|
这里将参数值赋值给了受保护的变量$data
,接下来继续回到savegalleryoptions
代码块
|
|
$_zp_gallery实例调用了save方法,从Gallery类中找到save方法,将$this->data值serialize序列化后传递给了setOption函数
|
|
跟进setOption函数,在文件/zenphoto-zenphoto-1.4.8/zp-core/functions-basic.php
|
|
这里我们传入$key
是gallery_data
,$value
是序列化后的$this->data
,从代码中可以看到这两个在INSERT INTO
前都要经过db_quote
函数处理,跟进db_quote
,我本地选择的mysqli
扩展,所以定位到此文件/zenphoto-zenphoto-1.4.8/zp-core/functions-db-MySQLi.php
|
|
这里的$_zp_DB_connection
即为我mysqli
连接串,接着$_zp_DB_connection->real_escape_string($string)
其实是一种面向对象的写法,面向过程的写法是这样的string mysqli_real_escape_string ( mysqli $link , string $escapestr )
,官方文档里有说明该函数过滤哪些字符。
看来db_quote
还是有用的,毕竟单双引号都被干掉了,所以这个输入点的是无法构造出带单双引号的payload的,回到漏洞复现的步骤,当时填入的payload
是id,extractvalue(0x0a,concat(0x0a,(select version())))%23
,这个payload是不受到过滤函数影响的,从数据库也可以看到payload被完整的插入进去了
到这里可以看到该二阶注入的第一步已经成功了,下面继续分析
访问
http://localhost:8888/zenphoto-zenphoto-1.4.8/zp-core/admin-upload.php?page=upload&tab=http&type=images
能触发漏洞,找到相关位置,/zenphoto-zenphoto-1.4.8/zp-core/admin-upload.php
,66行
genAlbumList($albumlist);
找到该函数实现的位置,文件/zenphoto-zenphoto-1.4.8/zp-core/admin-functions.php
|
|
$albumsprime = $_zp_gallery->getAlbums(0);
,这里有个我们熟悉的类实例$_zp_gallery
,跟进/zenphoto-zenphoto-1.4.8/zp-core/class-gallery.php
|
|
$sorttype
变量值为NULL
,注意这里的$key
参数,这个就是被我们污染了的变量,跟进下getAlbumSortKey
的由来,/zenphoto-zenphoto-1.4.8/zp-core/class-gallery.php
|
|
由于传入的$sorttype
为NULL
,所以进入了if
判断,调用getSortType
函数去了,跟进/zenphoto-zenphoto-1.4.8/zp-core/class-gallery.php
|
|
这里回去调用get
方法,并返回结果,所以找get
方法去
|
|
这里传入一个$field
,会返回$this->data
值,而这个$this->data
在构造函数中的实现为/zenphoto-zenphoto-1.4.8/zp-core/class-gallery.php
|
|
跟进getOption
函数/zenphoto-zenphoto-1.4.8/zp-core/functions-basic.php
|
|
它会去数据库的options
表里面去取对应$key
字段的value
并返回,而我们传入的$key=gallery_data
,得到value
字段值为
a:21:{s:21:"gallery_sortdirection";i:0;s:16:"gallery_sorttype";s:56:"id,extractvalue(0x0a,concat(0x0a,(select version())))%23";s:13:"gallery_title";s:31:"a:1:{s:5:"en_US";s:6:"相馆";}";s:19:"Gallery_description";s:99:"a:1:{s:5:"en_US";s:73:"You can insert your Gallery description on the Admin Options Gallery tab.";}";s:16:"gallery_password";N;s:12:"gallery_user";N;s:12:"gallery_hint";N;s:10:"hitcounter";N;s:13:"current_theme";s:7:"default";s:13:"website_title";s:0:"";s:11:"website_url";s:0:"";s:16:"gallery_security";s:6:"public";s:16:"login_user_field";N;s:24:"album_use_new_image_date";i:0;s:19:"thumb_select_images";i:0;s:17:"unprotected_pages";s:43:"a:2:{i:0;s:8:"register";i:1;s:7:"contact";}";s:13:"album_publish";i:1;s:13:"image_publish";i:1;s:30:"multilevel_thumb_select_images";i:0;s:14:"sort_direction";i:0;s:9:"codeblock";s:6:"a:0:{}”;}
回到构造函数,这个值赋值给$data
,然后传入getSerializedArray
函数,跟进一下/zenphoto-zenphoto-1.4.8/zp-core/functions-common.php
|
|
这里就是做一下简单的反序列化处理,以数组形式将结果返回。
这样就得到$this->data[‘gallery_sorttype’]
的值id,extractvalue(0x0a,concat(0x0a,(select version())))%23
,也就是$sorttype
的值为id,extractvalue(0x0a,concat(0x0a,(select version())))%23
,即$key
的值。
接下来又调用了sortAlbumArray
函数,找到该函数/zenphoto-zenphoto-1.4.8/zp-core/class-gallery.php
|
|
至此,二阶注入的第二步分析完,完整的过程就是这样,最后看下mysql的执行log,之前存入的payload被执行了即触发了漏洞。
总结
正如exploit-db
上所说的,ZenPhoto1.4.8
存在很多处二阶注入的地方。本质上仍然是在对用户可控的输入点过滤不够,过分依赖系统函数。