作者:i9n0re
簡介
看到了國外有大佬發了關于WordPress的一個非常有名的插件,contact form 7的漏洞,之前見到過很多WordPress站點使用這個插件,大佬寫的比較籠統,一些詳細的利用方式沒有說的太明白.
漏洞成因
這個漏洞是由于插件開發者對WordPress的使用不當造成的,其實跟WordPress的邏輯有一定的關系,導致了可以發布普通文章的用戶,可以繞過權限認證,進行發表原本插件作者只允許管理員創建和修改的自定義類型的"post"。
漏洞作者發現了contact from 7插件存在這樣的缺陷,導致了任意一個可以發表普通文章的用戶,可以新建一個contact,而且在5.0.3版本下,附件可以跨目錄進行添加文件,進而可以讀取網站的 wp-config.php。
相關的技術點
nonce
首先我們了解下什么是nonce?nonce相當于csrf token是WordPress用來防御csrf問題的,并進行了相關的權限驗證。
post_type
post_type是插件作者注冊的自定義post類型,與WordPress的文章類似,插件作者要實現一個頁面來進行管理文章類型。只有在后臺的新建或者編輯頁面當中可以獲取到nonce隨機數,提交的時候只有代入了nonce才能進行相應的操作。
漏洞詳情
以contact form 7 v5.0.3為例。
插件作者只允許WordPress的editor才能新建和編輯contact。

如果是文章的發布者,就沒有修改和創建權限,會顯示下面的頁面。

contact form 7也是一種自定義類型的 post ,數據里面都是存在了wp_posts表當中,通過 post_type進行區分。

正常情況,插件作者是通過 current_user_can('publish_pages') 進行權限的判定,也就是說editor以上的權限可以編輯,防止普通用戶打開新建和修改文章的頁面。
但是用戶仍可以操作普通的文檔,通過請求接口 wp-admin/post.php 的方式進行新建和編輯文章,只不過 post_type 變為了post等普通文檔類型。由于插件作者在 register_post_type 的時候沒有進行相關權限的配置,僅僅依靠了current_user_can('publish_pages')驗證用戶編輯權限,出現了安全問題。
接下來我們來看看普通文檔的新建、編輯流程。
正常流程上,普通文檔上傳接口是請求 post-new.php后先生成一個 post,然后再進行編輯,請求post.php,設置參數 action 為 editpost。
// wp-admin/post.php
case 'editpost':
check_admin_referer('update-post_' . $post_id);
$post_id = edit_post();
// Session cookie flag that the post was saved
if ( isset( $_COOKIE['wp-saving-post'] ) && $_COOKIE['wp-saving-post'] === $post_id . '-check' ) {
setcookie( 'wp-saving-post', $post_id . '-saved', time() + DAY_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, is_ssl() );
}
redirect_post($post_id); // Send user on their way while we keep working
exit();
通過函數 check_admin_referer 檢測nonce是否合法。
// wp-includes/pluggable.php
// Nonce generated 0-12 hours ago
$expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce'), -12, 10 );
if ( hash_equals( $expected, $nonce ) ) {
return 1;
}
// Nonce generated 12-24 hours ago
$expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
if ( hash_equals( $expected, $nonce ) ) {
return 2;
}
漏洞利用的地方是通過傳入action為post,調用下面這個邏輯。
// wp-admin/post.php
case 'post':
check_admin_referer( 'add-' . $post_type );
$post_id = 'postajaxpost' == $action ? edit_post() : write_post();
redirect_post( $post_id );
exit();
其中 post_type 是通過傳入的 post_id 去數據庫里面查詢得到。
// wp-admin/post.php
if ( $post_id )
$post = get_post( $post_id );
if ( $post ) {
$post_type = $post->post_type;
$post_type_object = get_post_type_object( $post_type );
}
可以看到,如果傳入的 post_id為正常帖子創建請求,這個地方的 nonce 普通用戶就可以通過頁面進行獲取了。
繞過nonce檢測
可以看到 post_id 是通過 $_GET['post'] 或者 $_POST['post_ID']兩種方式獲取。
// wp-admin/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;
所以如果我們構建一個存在的并且post_type為 post 的帖子ID作為參數傳入的話, check_admin_referer 的參數變為了固定值 add-post 這樣的話,如果我們拿到了nonce就可以繞過了檢測。有同學會問,怎么得到這個nonce呢?通過跟代碼,我發現在 dashboard 頁面當中,下面這個功能里面就有我們需要的nonce,通過查看源代碼,獲取這個表單的input這樣就繞過了檢測。
<input type="hidden" id="_wpnonce" name="_wpnonce" value="xxx" />

創建自定義類型的post
繞過了nonce檢測后,我們來看 post 那個case,流程會進入到 write_post 函數,然后我們看到這個邏輯。
// wp-includes/post.php
if ( !current_user_can( $ptype->cap->edit_posts ) ) {
if ( 'page' == $ptype->name )
return new WP_Error( 'edit_pages', __( 'Sorry, you are not allowed to create pages on this site.' ) );
else
return new WP_Error( 'edit_posts', __( 'Sorry, you are not allowed to create posts or drafts on this site.' ) );
}
問題就出現在這,因為作者在注冊post_type的時候沒有進行權限限制,導致了權限提升。
v5.0.3 的插件配置:
//wp-content/plugins/contact-form-7/includes/contact-form.php
public static function register_post_type() {
register_post_type( self::post_type, array(
'labels' => array(
'name' => __( 'Contact Forms', 'contact-form-7' ),
'singular_name' => __( 'Contact Form', 'contact-form-7' ),
),
'rewrite' => false,
'query_var' => false,
) );
}
就導致了如果繞過了nonce檢測,普通用戶也就可以成功的創建只有editer權限才可以創建的 contact form 7了。
進一步利用
權限提升已經完成,下面就是利用了contact from 7 v5.0.3的一個問題。

當發送郵件的時候,可以攜帶附件,但是這個附件可以跨目錄讀取,導致了用戶可以直接攜帶 wp-config.php 進行發送,實現敏感信息的泄露。
漏洞利用
理清了漏洞觸發邏輯,利用方式就簡單了,在后臺登錄頁面,直接引用 poc.js。
注意修改幾個參數
- 修改get請求,query參數的post為已存在的帖子ID。
- 修改post參數中,_wpnonce為上文說的獲取方式。
- 修改post參數中
meta_input[_mail][recipient]參數為自己的收件箱。 meta_input[_mail][attachments]這個參數代表著想要獲取的附件。- 其他的標題,主題什么的參數看情況自己修改。
然后可以在控制臺里面引用,會發現新建了一個表單,然后在帖子里面正常引用這個表單,再頁面中使用,并點擊發送后,在自己的收件箱當中收到 wp-config.php 的附件。

漏洞修復
在注冊 post_type 的時候,配置權限。
//wp-content/plugins/contact-form-7/includes/contact-form.php
public static function register_post_type() {
register_post_type( self::post_type, array(
'labels' => array(
'name' => __( 'Contact Forms', 'contact-form-7' ),
'singular_name' => __( 'Contact Form', 'contact-form-7' ),
),
'rewrite' => false,
'query_var' => false,
'capability_type' => 'page'
) );
}
如果進行了這樣的配置的話,在進行 write_post 這個函數邏輯的時候
// wp-includes/post.php
if ( !current_user_can( $ptype->cap->edit_posts ) ) {
if ( 'page' == $ptype->name )
return new WP_Error( 'edit_pages', __( 'Sorry, you are not allowed to create pages on this site.' ) );
else
return new WP_Error( 'edit_posts', __( 'Sorry, you are not allowed to create posts or drafts on this site.' ) );
}
這個判斷才會生效,導致權限認證失敗。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/774/
暫無評論