选择要插入的水印
第2步,在页面背景分组中单击水印按钮,并在打开的水印面板中选择合适的水印即可。
如果需要删除已经插入的水印,则再次单击水印面板,并单击删除水印按钮即可。
在Word2007文档中插入水印并且将文档背景设置为半透明的标识,就可以呈现出水印的效果。而在Word2007中,为用户提供了多种水印的样式,大家也可以现行选择插入。当然,水印可以是文字也可以是图片。
在前面我们初步了解了在word 2007文档中插入水印的设置方法,主要是通过自带的水印样式进行选择。如果在样式中没有找到自己想要的水印,那么大家可以自定义制作水印,可以是文字水印和图片水印。
一、添加文字水印
水印
在word 2007中打开需要添加水印的文档。在页面布局菜单下选择水印,我们可以看到word 2007为我们提供了一些水印。我们可以直接选择这些现成的水印,当然我们也可以做自己心仪的水印。
点击自定义水印,即弹出的对话框。我们选择文字水印就可以制作出自己想要的水印了。
在语言菜单中选择水印的语言种别,文字菜单中选择水印的文字内容,设置好水印文字的字体、字号、颜色、透明度和版式后,确定应用,就可以看到文本后面已经生成了设定的水印字样。
二、添加图片水印
在图1对话框中选择图片水印,然后点击选择图老师图片找到事先准备做水印用的图片。添加后,设置图片的缩放比例、是否冲蚀。冲蚀的作用是让添加的图片在文字后面降低透明度显示,以免影响文字的显示效果。
三、打印水印
在打印预览中可预览制作的水印效果,然后设置打印选项。在打印选项内选中背景色和图像,水印便会一同打出。
在word 2007文档中,自定义制作水印的方法,是通过页面布局菜单下选择水印后,点击自定义水印,之后,就可以根据对话框的提示内容进行添加设置。怎么样,自定义水印的设置学会了吧。
在一些需要吸引人眼球的文档中,往往将文档的背景进行颜色的设置,这就要对页面背景进行tulaoShi.com设置。在Word2007文档中可以设置单色的页面背景颜色也可以使渐变背景颜色,现在就先来讲讲单色的页面背景颜色怎么设置。
第1步,打开Word2007文档窗口,切换到页面布局选项卡。
页面颜色面板
第2步,在页面背景分组中单击页面颜色按钮,并在打开的页面颜色面板中选择主题颜色或标准颜中的特定颜色。
小提示:如果主题颜色和标准色中显示的颜色依然无法满足用户的需要,可以单击其他颜色按钮,在打开的颜色对话框中切换到自定义选项卡,并选择合适的颜色。设置完毕单击确定按钮即可。
页面背景顾名思义,要从页面布局选项卡中进入,并选择页面颜色,要对主题颜色或者标准颜中的特定颜色进行选择。当然,在颜色选择中,还可以自定义颜色。
在Word2007文档中,对编辑好的文档进行页面背景的设置,可以单色也可以是渐变背景颜色,而文档的边框也是可以随用户来修改的。那么在Word2007文档对页面边框的设置的具体操作如下:
第1步,打开Word2007文档窗口,切换到页面布局选项卡。在页面设置分组中单击页面边框按钮,如图所示。
页面边框
第2步,在打开的边框和底纹对话框中切换到页面边框选项卡,然后在样式列表或艺术型列表中选择边框样式,并设置边框宽度。设置完毕单击确定按钮,如图所示。
边框和底纹
从上方图老师小编为大家介绍的在Word2007文档中设置页面边框的操作步骤很简单吧,只需要两个步骤。而操作的突破口就是页面布局选项卡,在选项卡里面,可以找到页面设置,然后,就是选择页面边框的按钮,就可以进行具体修改。
知道了在Word2007文档中如何设置单色页面背景后,会发现,单色页面背景看起来总是单调写,很难让人眼前一亮的效果,那么大家就可以考虑考虑设置简便页面背景颜色。操作方法也很简单。
第1步,打开Word2007文档窗口,切换到页面布局选项卡。在页面背景分组中单击页面颜色按钮,并在打开的页面颜色面板中选择填充效果命令。
选择填充效果命令
第2步,在打开的填充效果对话框中,切换到渐变选项卡。在颜色区域选中双色单选框,然后分别选择颜色1和颜色2。在底纹样式区域选择颜色的渐变方向,包括水平、垂直、斜上、斜下、角部辐射和中心辐射几种样式。设置完毕单击确定按钮即可。
在Word2007文档中设置单色页面背景颜色还是渐变背景颜色,都是在页面背景项中进行操作,不同的是,在页面颜色选择中,设置渐变背景颜色要选择填充效果命令,其次,要对颜色的渐变方向进行选择设置。
我们在Word中编辑文档的话,常常系统会自动将文档进行分页,也就是说,当一页中的字数已达到一定数时,就会跳到下一页。如果我们一篇编辑好的文档,在其中的某一部分开始进行分页,就得用到分页符。
方式1:打开Word2007文档窗口,将插入点定位到需要分页的位置。切换到页面布局功能区。在页面设置分组中单击分隔符按钮,并在打(图老师整理)开的分隔符下拉列表中选择分页符选项。
选择分页符选项
方式2:打开Word2007文档窗口,将插入点定位到需要分页的位置。切换到插入功能区。在页分组中单击分页按钮即可。
单击分页按钮
方式3:打开Word2007文档窗口,将插入点定位到需要分页的位置。按下Ctrl+Enter组合键插入分页。
在Word2007文档中,将从文档的某一部分开始分页,就要在其位置上插入分页符,而上方为大家介绍了三种插分页符的技巧。分页符插入后,被分开的文档处于两个不同的页面,而文档的字体,版式等不会随着插入而改变。
WordPress核心功能SQL注入漏洞分析
威胁响应中心研究员对Wordpress核心功能SQL注入漏洞(编号为CVE-2015-5623和CVE-2015-2213)进行了详细的分析
0x00 漏洞概述
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/wjc/)在twitter上看到Wordpress核心功能出现SQL注入漏洞,想学习下,就深入的跟了下代码,结果发现老外留了好大的一个坑。虽然确实存在注入问题,但是却没有像他blog中所说的那样可以通过订阅者这样的低权限来触发SQL注入漏洞。
这个Wordpress漏洞系列的文章目前更新了两个部分,一个是通过权限绕过实现订阅者权限写一篇文章到回收站,另一个就是通过写入的这篇文章来 实现SQL注入漏洞。这两个漏洞的描述,TSRC的phithon写的文章其实已经很清楚了,我这里从我分析的角度来介绍这两个漏洞的形成、利用以及 phithon省略掉的原文部分内容。
0x01 越权提交文章
_wpnonce的获取
在讲越权漏洞之前,我们需要介绍一下Wordpress后台的_wpnonce参数,这个参数主要是用来防止CSRF攻击的token。后台大多数 敏感功能都会通过,当前用户信息、功能名称、操作对象id等内容生成token,所以我们很难在没有token的时候进行某些功能的使用。这个CSRF的 防护机制间接地导致之后SQL注入很难再低权限情况下触发(因为看不到token),后面讲SQL注入漏洞时,我们会详细的聊这个问题有多坑。
之所以要把_wpnonce的获取放在前面讲,是因为,我们需要一个让我们可以使用编辑提交文章功能的token。这个功能的token我们可以通 过访问后台post.php的post-quickdraft-save功能来获取,严格的来说这个获取方式也算是一个信息泄露漏洞,官方也在新版本中进 行了修补。下面我我们来看下这个token泄露的原因。部分代码如下:
case 'post-quickdraft-save':// Check nonce and capabilities$nonce = $_REQUEST['_wpnonce'];$error_msg = false; // For output of the quickdraft dashboard widgetrequire_once ABSPATH . 'wp-admin/includes/dashboard.php'; if ( ! wp_verify_nonce( $nonce, 'add-post' ) )$error_msg = __( 'Unable to submit this form, please refresh and try again.' ); if ( ! current_user_can( 'edit_posts' ) )$error_msg = __( 'Oops, you don’t have access to add new drafts.' ); if ( $error_msg )return wp_dashboard_quick_press( $error_msg );
从上面代码我们可以看出这个功能在发现出现错误时,会将错误信息通过wp_dashboard_quick_press这个函数打印到页面上,而这个函数生成的页面的代码中有这样一行:
PHP wp_nonce_field( 'add-post' ); 1 wp_nonce_field('add-post');
生成了add-post功能的_wpnonce,也就是说,即使我们做了一些被禁止的操作,返回页面中也会出现这个_wpnonce。
越权提交与条件竞争
如phithon文章所说,由于GP使用逻辑混乱而导致的验证绕过。我们来看post.php文件在开始检验文章是否存在的代码:
if ( isset( $_GET['post'] ) )$post_id = $post_ID = (int) $_GET['post'];elseif ( isset( $_POST['post_ID'] ) )$post_id = $post_ID = (int) $_POST['post_ID'];else$post_id = $post_ID = 0; global $post_type, $post_type_object, $post; if ( $post_id )$post = get_post( $post_id ); if ( $post ) {$post_type = $post-post_type;$post_type_object = get_post_type_object( $post_type );}
可以看到在先提取GET中的post参数作为文章id来提取文章信息,如果没有GET的post参数,再去用POST的post_ID参数来提取。而真正检验用户是否对文章具有编辑权限,则是在edit_post中进行的,而这里的判断参数是从POST中提取的:
function edit_post( $post_data = null ) {global $wpdb; if ( empty($post_data) )$post_data = &$_POST; // Clear out any data in internal vars.unset( $post_data['filter'] ); $post_ID = (int) $post_data['post_ID'];$post = get_post( $post_ID );$post_data['post_type'] = $post-post_type;$post_data['post_mime_type'] = $post-post_mime_type; if ( ! empty( $post_data['post_status'] ) ) {$post_data['post_status'] = sanitize_key( $post_data['post_status'] ); if ( 'inherit' == $post_data['post_status'] ) {unset( $post_data['post_status'] );}} $ptype = get_post_type_object($post_data['post_type']);if ( !current_user_can( 'edit_post', $post_ID ) ) {if ( 'page' == $post_data['post_type'] )wp_die( __('You are not allowed to edit this page.' ));elsewp_die( __('You are not allowed to edit this post.' ));}
我们继续看这段代码最后的if判断,判断当前用户是否有编辑这篇文章的权限。这个判断最终的操作是在map_meta_cap这个函数中进行的:
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/wjc/)case 'edit_post':case 'edit_page':$post = get_post( $args[0] );if ( empty( $post ) )break; if ( 'revision' == $post-post_type ) {$post = get_post( $post-post_parent );}
可以看出如果文章是不存在的,那么就会break出switch,在函数结束时返回$caps变量,而$caps在函数开始的时候已经被定义为一个 空的数组了,也就是说,这里会返回一个空数组。下面我们来向前走,返回到调用map_meta_cap的has_cap函数中,看看后续的操作
public function has_cap( $cap ) {if ( is_numeric( $cap ) ) {_deprecated_argument( __FUNCTION__, '2.0', __('Usage of user levels by plugins and themes is deprecated. Use roles and capabilities instead.') );$cap = $this-translate_level_to_cap( $cap );} $args = array_slice( func_get_args(), 1 );$args = array_merge( array( $cap, $this-ID ), $args );$caps = call_user_func_array( 'map_meta_cap', $args ); // Multisite super admin has all caps by definition, Unless specifically denied.if ( is_multisite() && is_super_admin( $this-ID ) ) {if ( in_array('do_not_allow', $caps) ) return false;return true;} $capabilities = apply_filters( 'user_has_cap', $this-allcaps, $caps, $args, $this );$capabilities['exist'] = true; // Everyone is allowed to existforeach ( (array) $caps as $cap ) {if ( empty( $capabilities[ $cap ] ) ) return false;} return true;}
看最后那个foreach,它检测$caps中的各个元素在$capabilities中是否有不存在的,如果有那么就return false,但是$caps是个空数组,这样很容易我们就获得了一个true,权限校验成功绕过。那么这样我们可以得到的一个信息就是,我们可以通过这个缺陷去尝试update一个并不存在的文章。
那么现在问题来了,直接update一个不存在的文章是没有意义的,因为数据库执行SQL是肯定会报错,我们要怎么才能成功创建一篇文章呢?
在校验权限之后,数据库执行操作之前,post.php中有这样一段代码:
if ( isset( $post_data['tax_input'] ) ) {foreach ( (array) $post_data['tax_input'] as $taxonomy = $terms ) {// Hierarchical taxonomy data is already sent as term IDs, so no conversion is necessary.if ( is_taxonomy_hierarchical( $taxonomy ) ) { continue;} if ( ! is_array( $terms ) ) { $comma = _x( ',', 'tag delimiter' ); if ( ',' !== $comma ) { $terms = str_replace( $comma, ',', $terms ); } $terms = explode( ',', trim( $terms, " ntrx0B," ) );} $clean_terms = array();foreach ( $terms as $term ) { // Empty terms are invalid input. if ( empty( $term ) ) { continue; } $_term = get_terms( $taxonomy, array( 'name' = $term, 'fields' = 'ids', 'hide_empty' = false, ) );
如果在POST的数据中存在名为tax_input的数组参数,那么就对参数中的值使用逗号进行切割,然后对分割出来的每个内容进行一次select查询。那么这里我们可以想象一下,如果我们post_ID中填写的是对当前最新文章ID+1的数值,并且在tax_input这个参数添加特别多的内容,导致它不停地select查询,我们是不是在这个停滞的时间当中插入一篇文章(这篇文章的ID刚好是最新文章ID+1),那么后面的update是不是就有意义了?
下面最后一个问题,我们如何插入一篇文章呢?
还记得post-quickdraft-save功能吗?它的作用就是快速的保存一篇草稿!
这里可耻的盗一张phithon文章中的一幅图,来让各位加深条件竞争的流程:
小结
在调试过程中,感触最深的的就是条件竞争,要很好的把握插入文章和update的时间差。如果查得太早则无法通过权限检验,如果太晚了又失去了意 义。我获得的经验是,**插入文章的进程尽量等待时间长一些,tax_input中的内容尽可能的多,这样能够比较稳定的在校验之后,update之前插 入文章。
当然这里面还有每个用户只可以保存一个草稿的限制,漏洞发现者提供的建议是等一周的时间,草稿会自动删除,而_wpnonce则会多保留一天。phithon的建议更好一些,使用两个账户,一个账户插入文章,一个账户update。
一个越权漏洞由信息泄露、逻辑漏洞导致的权限绕过以及程序执行时间可操控三个小漏洞组合而成,环环相扣,脑洞实在是大。
0x02 SQL注入漏洞
revision trick
漏洞发现者的文章最开始提到了一个revision的trick,用于承接越权写文章之后如何继续进行深入攻击。但是由于这个老外隐藏了部分技巧, 导致按照他文章所说的内容无法复现订阅者账号触发SQL注入漏洞,所以phithon在文章中没有提到这个trick。我在这里本着还原作者思路的目的, 向大家介绍一下关于这个trick的原理。
下面是原文关于这个trick部分的翻译:
revisions字段用于记录草稿或者更新的发布,Wordpress中用revisions记录对于完成的提交和存储都会在posts数据库表 中将post_type设置成revision,每个revision都会有一个’post_parent’字段,指明这个revision所基于的原版 提交。
当尝试编辑一个revision时,会通过post_parent来进行校验,而不是revision本身。这样,如果我们基于一个post创建一 个revision,那么我们可以任意设置它的状态为trash之外的任何状态,即使原始的post的状态是trash
使用这个trick,我们能够编辑这个puppet revision(傀儡文章?)和自由的向他添加评论,即使他的原版post已经被丢弃到了垃圾箱当中。
结合我们上文的越权写漏洞,可以看出作者提出使用这个trick的目的是为了,利用我们之前写进垃圾箱中文章(post)的修订版 (revision)来进行编辑和评论进行操纵。因为类型为post的文章,即使是当前订阅用户提交的,也是没有权限编辑的,而revision却是可以 编辑的。所以按照原文中所说,我们可以使用这个trick继续后面的操作。
漏洞原因
这个漏洞其实是一个二次注入的漏洞,它的本质原因是在从垃圾箱还原文章时,也要还原文章下面的评论,而在还原评论的代码中存在直接拼接用户可控内容,从而导致SQL注入漏洞。下面为问题代码:
function wp_untrash_post_comments( $post = null ) {global $wpdb; $post = get_post($post);if ( empty($post) )return; $post_id = $post-ID; $statuses = get_post_meta($post_id, '_wp_trash_meta_comments_status', true); if ( empty($statuses) )return true; do_action( 'untrash_post_comments', $post_id ); // Restore each comment to its original status.$group_by_status = array();foreach ( $statuses as $comment_id = $comment_status )$group_by_status[$comment_status][] = $comment_id; foreach ( $group_by_status as $status = $comments ) {// Sanity check. This shouldn't happen.if ( 'post-trashed' == $status )$status = '0';$comments_in = implode( "', '", $comments );$wpdb-query( "UPDATE $wpdb-comments SET comment_approved = '$status' WHERE comment_ID IN ('" . $comments_in . "')" );} clean_comment_cache( array_keys($statuses) ); delete_post_meta($post_id, '_wp_trash_meta_comments_status'); do_action( 'untrashed_post_comments', $post_id );}
从代码中我们可以看到$status和$comments变量的内容会拼接到SQL中,而这两个变量的内容是用户可以操控的。因为用户传入的数据会先被过滤处理后存储到数据库中,然后直接从数据库提取数据进行拼接,所以如果我们的攻击语句会在数据库提取时恢复本来面貌标准的二次注入。
漏洞利用&对作者深深的怨念
看懂了revision和漏洞原因,利用应该很简单:
对写入文章的revision进行评论编辑评论状态为注入攻击语句将revision丢入垃圾箱还原最开始的文章(revision的原版)看似没有什么问题,不知道各位还记不记得我们上文说的_wpnonce。对!就是这个坑!作者通篇没有提到这个漏洞要怎么获取_wpnonce!这最直接的导致我们第1、2、4步没有办法玩了o(╯□╰)o。
我猜测作者没有提及这个问题,要么是他自己也没找到,这篇文章其实只是个标题党,要么是他藏了一手。从作者前后漏洞关联的那么紧密来看,我比较倾向于后一种原因,所以这里要幽怨的瞪他一眼~~
小结
作者利用读取数据库中存储内容没有进行过滤的盲点进行二次注入的思路比较直接,但是利用revision编辑垃圾箱中文章这个点真心赞。
在分析过程中发现revision的编辑并非向作者所说的可以改成trash之外的任意类型,文章状态大概有publish、future、private、inherit、auto-draft、attachment、draft、pedding、trash这几种,而我们所能修改的文章类型只能是inherit、pedding和draft这三种。否则,如果我可以修改文章状态为private,从前台完成利用第1,2步操作.
断断续续的跟了这个漏洞一周的时间了,最大的感受就是作者真心是个坑o(╯□╰)o
0x03 总结
跟这个漏洞体会最深的就是Wordpress的token机制在防护csrf的同时,也协助防御了其他类型的攻击,一个很好的机制。
GP操作的逻辑是Web代码的一个问题,尤其是在做一些权限校验时候,很容易出现Wordpress这样的GP交叉导致的逻辑混乱问题。
二次注入应该算是一个老生常谈的问题了,过于信任已记录的数据,最终会导致忘记这条记录其实是用户写入的。