南通颐猩文化传播有限公司

當(dāng)前位置:首頁(yè) >  站長(zhǎng) >  建站經(jīng)驗(yàn) >  正文

WordPress SQL注入漏洞與提權(quán)分析

 2015-08-25 14:43  來(lái)源: 綠盟科技博客   我來(lái)投稿 撤稿糾錯(cuò)

  域名預(yù)訂/競(jìng)價(jià),好“米”不錯(cuò)過(guò)

WordPress核心功能SQL注入漏洞分析

威脅響應(yīng)中心研究員對(duì)Wordpress核心功能SQL注入漏洞(編號(hào)為CVE-2015-5623和CVE-2015-2213)進(jìn)行了詳細(xì)的分析

0x00 漏洞概述

在twitter上看到Wordpress核心功能出現(xiàn)SQL注入漏洞,想學(xué)習(xí)下,就深入的跟了下代碼,結(jié)果發(fā)現(xiàn)老外留了好大的一個(gè)坑。雖然確實(shí)存在注入問(wèn)題,但是卻沒(méi)有像他blog中所說(shuō)的那樣可以通過(guò)訂閱者這樣的低權(quán)限來(lái)觸發(fā)SQL注入漏洞。

這個(gè)Wordpress漏洞系列的文章目前更新了兩個(gè)部分,一個(gè)是通過(guò)權(quán)限繞過(guò)實(shí)現(xiàn)訂閱者權(quán)限寫一篇文章到回收站,另一個(gè)就是通過(guò)寫入的這篇文章來(lái)實(shí)現(xiàn)SQL注入漏洞。這兩個(gè)漏洞的描述,TSRC的phithon寫的文章其實(shí)已經(jīng)很清楚了,我這里從我分析的角度來(lái)介紹這兩個(gè)漏洞的形成、利用以及phithon省略掉的原文部分內(nèi)容。

0x01 越權(quán)提交文章

_wpnonce的獲取

在講越權(quán)漏洞之前,我們需要介紹一下Wordpress后臺(tái)的_wpnonce參數(shù),這個(gè)參數(shù)主要是用來(lái)防止CSRF攻擊的token。后臺(tái)大多數(shù)敏感功能都會(huì)通過(guò),當(dāng)前用戶信息、功能名稱、操作對(duì)象id等內(nèi)容生成token,所以我們很難在沒(méi)有token的時(shí)候進(jìn)行某些功能的使用。這個(gè)CSRF的防護(hù)機(jī)制間接地導(dǎo)致之后SQL注入很難再低權(quán)限情況下觸發(fā)(因?yàn)榭床坏絫oken),后面講SQL注入漏洞時(shí),我們會(huì)詳細(xì)的聊這個(gè)問(wèn)題有多坑。

之所以要把_wpnonce的獲取放在前面講,是因?yàn)椋覀冃枰粋€(gè)讓我們可以使用編輯提交文章功能的token。這個(gè)功能的token我們可以通過(guò)訪問(wèn)后臺(tái)post.php的post-quickdraft-save功能來(lái)獲取,嚴(yán)格的來(lái)說(shuō)這個(gè)獲取方式也算是一個(gè)信息泄露漏洞,官方也在新版本中進(jìn)行了修補(bǔ)。下面我我們來(lái)看下這個(gè)token泄露的原因。部分代碼如下:

case 'post-quickdraft-save':

// Check nonce and capabilities

$nonce = $_REQUEST['_wpnonce'];

$error_msg = false;

// For output of the quickdraft dashboard widget

require_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 );

從上面代碼我們可以看出這個(gè)功能在發(fā)現(xiàn)出現(xiàn)錯(cuò)誤時(shí),會(huì)將錯(cuò)誤信息通過(guò)wp_dashboard_quick_press這個(gè)函數(shù)打印到頁(yè)面上,而這個(gè)函數(shù)生成的頁(yè)面的代碼中有這樣一行:

wp_nonce_field( 'add-post' );

生成了add-post功能的_wpnonce,也就是說(shuō),即使我們做了一些被禁止的操作,返回頁(yè)面中也會(huì)出現(xiàn)這個(gè)_wpnonce。

越權(quán)提交與條件競(jìng)爭(zhēng)

如phithon文章所說(shuō),由于GP使用邏輯混亂而導(dǎo)致的驗(yàn)證繞過(guò)。我們來(lái)看post.php文件在開始檢驗(yàn)文章是否存在的代碼:

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參數(shù)作為文章id來(lái)提取文章信息,如果沒(méi)有GET的post參數(shù),再去用POST的post_ID參數(shù)來(lái)提取。而真正檢驗(yàn)用戶是否對(duì)文章具有編輯權(quán)限,則是在edit_post中進(jìn)行的,而這里的判斷參數(shù)是從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.' ));

else

wp_die( __('You are not allowed to edit this post.' ));

}

我們繼續(xù)看這段代碼最后的if判斷,判斷當(dāng)前用戶是否有編輯這篇文章的權(quán)限。這個(gè)判斷最終的操作是在map_meta_cap這個(gè)函數(shù)中進(jìn)行的:

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 );

}

可以看出如果文章是不存在的,那么就會(huì)break出switch,在函數(shù)結(jié)束時(shí)返回$caps變量,而$caps在函數(shù)開始的時(shí)候已經(jīng)被定義為一個(gè)空的數(shù)組了,也就是說(shuō),這里會(huì)返回一個(gè)空數(shù)組。下面我們來(lái)向前走,返回到調(diào)用map_meta_cap的has_cap函數(shù)中,看看后續(xù)的操作

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 exist

foreach ( (array) $caps as $cap ) {

if ( empty( $capabilities[ $cap ] ) )

return false;

}

return true;

}

看最后那個(gè)foreach,它檢測(cè)$caps中的各個(gè)元素在$capabilities中是否有不存在的,如果有那么就return false,但是$caps是個(gè)空數(shù)組,這樣很容易我們就獲得了一個(gè)true,權(quán)限校驗(yàn)成功繞過(guò)。那么這樣我們可以得到的一個(gè)信息就是,我們可以通過(guò)這個(gè)缺陷去嘗試update一個(gè)并不存在的文章。

那么現(xiàn)在問(wèn)題來(lái)了,直接update一個(gè)不存在的文章是沒(méi)有意義的,因?yàn)閿?shù)據(jù)庫(kù)執(zhí)行SQL是肯定會(huì)報(bào)錯(cuò),我們要怎么才能成功創(chuàng)建一篇文章呢?

在校驗(yàn)權(quán)限之后,數(shù)據(jù)庫(kù)執(zhí)行操作之前,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, " \n\t\r\x0B," ) );

}

$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的數(shù)據(jù)中存在名為tax_input的數(shù)組參數(shù),那么就對(duì)參數(shù)中的值使用逗號(hào)進(jìn)行切割,然后對(duì)分割出來(lái)的每個(gè)內(nèi)容進(jìn)行一次select查詢。那么這里我們可以想象一下,如果我們post_ID中填寫的是對(duì)當(dāng)前最新文章ID+1的數(shù)值,并且在tax_input這個(gè)參數(shù)添加特別多的內(nèi)容,導(dǎo)致它不停地select查詢,我們是不是在這個(gè)停滯的時(shí)間當(dāng)中插入一篇文章(這篇文章的ID剛好是最新文章ID+1),那么后面的update是不是就有意義了?

下面最后一個(gè)問(wèn)題,我們?nèi)绾尾迦胍黄恼履?還記得post-quickdraft-save功能嗎?它的作用就是快速的保存一篇草稿!

這里可恥的盜一張phithon文章中的一幅圖,來(lái)讓各位加深條件競(jìng)爭(zhēng)的流程:

小結(jié)

在調(diào)試過(guò)程中,感觸最深的的就是條件競(jìng)爭(zhēng),要很好的把握插入文章和update的時(shí)間差。如果查得太早則無(wú)法通過(guò)權(quán)限檢驗(yàn),如果太晚了又失去了意義。我獲得的經(jīng)驗(yàn)是,**插入文章的進(jìn)程盡量等待時(shí)間長(zhǎng)一些,tax_input中的內(nèi)容盡可能的多,這樣能夠比較穩(wěn)定的在校驗(yàn)之后,update之前插入文章。

當(dāng)然這里面還有每個(gè)用戶只可以保存一個(gè)草稿的限制,漏洞發(fā)現(xiàn)者提供的建議是等一周的時(shí)間,草稿會(huì)自動(dòng)刪除,而_wpnonce則會(huì)多保留一天。phithon的建議更好一些,使用兩個(gè)賬戶,一個(gè)賬戶插入文章,一個(gè)賬戶update。

一個(gè)越權(quán)漏洞由信息泄露、邏輯漏洞導(dǎo)致的權(quán)限繞過(guò)以及程序執(zhí)行時(shí)間可操控三個(gè)小漏洞組合而成,環(huán)環(huán)相扣,腦洞實(shí)在是大。

0x02 SQL注入漏洞

revision trick

漏洞發(fā)現(xiàn)者的文章最開始提到了一個(gè)revision的trick,用于承接越權(quán)寫文章之后如何繼續(xù)進(jìn)行深入攻擊。但是由于這個(gè)老外隱藏了部分技巧,導(dǎo)致按照他文章所說(shuō)的內(nèi)容無(wú)法復(fù)現(xiàn)訂閱者賬號(hào)觸發(fā)SQL注入漏洞,所以phithon在文章中沒(méi)有提到這個(gè)trick。我在這里本著還原作者思路的目的,向大家介紹一下關(guān)于這個(gè)trick的原理。

下面是原文關(guān)于這個(gè)trick部分的翻譯:

revisions字段用于記錄草稿或者更新的發(fā)布,Wordpress中用revisions記錄對(duì)于完成的提交和存儲(chǔ)都會(huì)在posts數(shù)據(jù)庫(kù)表中將post_type設(shè)置成revision,每個(gè)revision都會(huì)有一個(gè)’post_parent’字段,指明這個(gè)revision所基于的原版提交。

當(dāng)嘗試編輯一個(gè)revision時(shí),會(huì)通過(guò)post_parent來(lái)進(jìn)行校驗(yàn),而不是revision本身。這樣,如果我們基于一個(gè)post創(chuàng)建一個(gè)revision,那么我們可以任意設(shè)置它的狀態(tài)為“trash”之外的任何狀態(tài),即使原始的post的狀態(tài)是“trash”

使用這個(gè)trick,我們能夠編輯這個(gè)“puppet revision”(傀儡文章?)和自由的向他添加評(píng)論,即使他的原版post已經(jīng)被丟棄到了垃圾箱當(dāng)中。

結(jié)合我們上文的越權(quán)寫漏洞,可以看出作者提出使用這個(gè)trick的目的是為了,利用我們之前寫進(jìn)垃圾箱中文章(post)的修訂版(revision)來(lái)進(jìn)行編輯和評(píng)論進(jìn)行操縱。因?yàn)轭愋蜑閜ost的文章,即使是當(dāng)前訂閱用戶提交的,也是沒(méi)有權(quán)限編輯的,而revision卻是可以編輯的。所以按照原文中所說(shuō),我們可以使用這個(gè)trick繼續(xù)后面的操作。

漏洞原因

這個(gè)漏洞其實(shí)是一個(gè)二次注入的漏洞,它的本質(zhì)原因是在從垃圾箱還原文章時(shí),也要還原文章下面的評(píng)論,而在還原評(píng)論的代碼中存在直接拼接用戶可控內(nèi)容,從而導(dǎo)致SQL注入漏洞。下面為問(wèn)題代碼:

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變量的內(nèi)容會(huì)拼接到SQL中,而這兩個(gè)變量的內(nèi)容是用戶可以操控的。因?yàn)橛脩魝魅氲臄?shù)據(jù)會(huì)先被過(guò)濾處理后存儲(chǔ)到數(shù)據(jù)庫(kù)中,然后直接從數(shù)據(jù)庫(kù)提取數(shù)據(jù)進(jìn)行拼接,所以如果我們的攻擊語(yǔ)句會(huì)在數(shù)據(jù)庫(kù)提取時(shí)恢復(fù)本來(lái)面貌——標(biāo)準(zhǔn)的二次注入。

漏洞利用&對(duì)作者深深的怨念

看懂了revision和漏洞原因,利用應(yīng)該很簡(jiǎn)單:

對(duì)寫入文章的revision進(jìn)行評(píng)論

編輯評(píng)論狀態(tài)為注入攻擊語(yǔ)句

將revision丟入垃圾箱

還原最開始的文章(revision的原版)

看似沒(méi)有什么問(wèn)題,不知道各位還記不記得我們上文說(shuō)的_wpnonce。對(duì)!就是這個(gè)坑!作者通篇沒(méi)有提到這個(gè)漏洞要怎么獲取_wpnonce!這最直接的導(dǎo)致我們第1、2、4步?jīng)]有辦法玩了o(╯□╰)o。

我猜測(cè)作者沒(méi)有提及這個(gè)問(wèn)題,要么是他自己也沒(méi)找到,這篇文章其實(shí)只是個(gè)標(biāo)題黨,要么是他藏了一手。從作者前后漏洞關(guān)聯(lián)的那么緊密來(lái)看,我比較傾向于后一種原因,所以這里要幽怨的瞪他一眼~~

小結(jié)

作者利用讀取數(shù)據(jù)庫(kù)中存儲(chǔ)內(nèi)容沒(méi)有進(jìn)行過(guò)濾的盲點(diǎn)進(jìn)行二次注入的思路比較直接,但是利用revision編輯垃圾箱中文章這個(gè)點(diǎn)真心贊。

在分析過(guò)程中發(fā)現(xiàn)revision的編輯并非向作者所說(shuō)的可以改成trash之外的任意類型,文章狀態(tài)大概有publish、future、private、inherit、auto-draft、attachment、draft、pedding、trash這幾種,而我們所能修改的文章類型只能是inherit、pedding和draft這三種。否則,如果我可以修改文章狀態(tài)為private,從前臺(tái)完成利用第1,2步操作.

斷斷續(xù)續(xù)的跟了這個(gè)漏洞一周的時(shí)間了,最大的感受就是作者真心是個(gè)坑o(╯□╰)o

0x03 總結(jié)

跟這個(gè)漏洞體會(huì)最深的就是Wordpress的token機(jī)制在防護(hù)csrf的同時(shí),也協(xié)助防御了其他類型的攻擊,一個(gè)很好的機(jī)制。

GP操作的邏輯是Web代碼的一個(gè)問(wèn)題,尤其是在做一些權(quán)限校驗(yàn)時(shí)候,很容易出現(xiàn)Wordpress這樣的GP交叉導(dǎo)致的邏輯混亂問(wèn)題。

二次注入應(yīng)該算是一個(gè)老生常談的問(wèn)題了,過(guò)于信任已記錄的數(shù)據(jù),最終會(huì)導(dǎo)致忘記這條記錄其實(shí)是用戶寫入的。

申請(qǐng)創(chuàng)業(yè)報(bào)道,分享創(chuàng)業(yè)好點(diǎn)子。點(diǎn)擊此處,共同探討創(chuàng)業(yè)新機(jī)遇!

相關(guān)標(biāo)簽
wordpress
sql優(yōu)化

相關(guān)文章

  • 分區(qū)表場(chǎng)景下的 SQL 優(yōu)化

    這篇文章主要介紹了分區(qū)表場(chǎng)景下的SQL優(yōu)化,幫助大家更好的理解和學(xué)習(xí)SQL,感興趣的朋友可以了解下

    標(biāo)簽:
    sql優(yōu)化
  • UCloud高可用數(shù)據(jù)庫(kù)UDB主從復(fù)制延時(shí)的解決

    MySQL主從復(fù)制的延時(shí)一直是業(yè)界困擾已久的問(wèn)題。延時(shí)的出現(xiàn)會(huì)降低主從讀寫分離的價(jià)值,不利于數(shù)據(jù)實(shí)時(shí)性較高的業(yè)務(wù)使用MySQL。UDB是UCloud推出的云數(shù)據(jù)庫(kù)服務(wù),上線已達(dá)六年,運(yùn)營(yíng)了數(shù)以萬(wàn)計(jì)的UDBMySQL實(shí)例

  • 如何修復(fù)網(wǎng)站漏洞之metinfo遠(yuǎn)程SQL注入漏洞

    2018年11月23日SINE網(wǎng)站安全檢測(cè)平臺(tái),檢測(cè)到MetInfo最新版本爆出高危漏洞,危害性較大,影響目前MetInfo5.3版本到最新的MetInfo6.1.3版本,該網(wǎng)站漏洞產(chǎn)生的主要原因是MetInfo的上傳代碼里的參數(shù)值沒(méi)有進(jìn)行安全過(guò)濾,導(dǎo)致上傳路徑這里進(jìn)行偽造路徑,并可以插入惡意的代碼

  • 虛擬主機(jī)mysql數(shù)據(jù)庫(kù)三種導(dǎo)入方法

    在導(dǎo)入mysql數(shù)據(jù)庫(kù)操作時(shí)通常會(huì)出現(xiàn)一些錯(cuò)誤導(dǎo)致數(shù)據(jù)庫(kù)導(dǎo)入失敗,這里以老牌虛擬主機(jī)提供商第一主機(jī)(http://www.5778.com)的虛擬空間為例,演示介紹三種mysql數(shù)據(jù)庫(kù)的正確導(dǎo)入方法。1、采用phpmyadmin導(dǎo)入:虛擬主機(jī)接入商在提供mysql數(shù)據(jù)庫(kù)的同時(shí)都會(huì)配備phpmyadm

    標(biāo)簽:
    sql優(yōu)化
  • MySQL技術(shù)嘉年華會(huì)2016即將召開

    會(huì)議通知大會(huì)簡(jiǎn)介2016年MySQL技術(shù)嘉年華將于2016年04月24日在上海舉行,本次大會(huì)由IMG(InsideMySQLGroup)社區(qū)主辦,專注于MySQL數(shù)據(jù)庫(kù)最佳應(yīng)用與實(shí)踐的技術(shù)分享,將為數(shù)據(jù)庫(kù)技術(shù)愛(ài)好者、應(yīng)用開發(fā)人員、企業(yè)架構(gòu)師們帶來(lái)最具技術(shù)價(jià)值的饕餮大餐。毫無(wú)疑問(wèn),MySQL是最為流行

    標(biāo)簽:
    sql優(yōu)化

熱門排行

信息推薦