PHP Object injection trên WordPress – Phần II

Lỗ hổng tồn tại trên wordpress
Như chúng ta đã biết, một số thông tin của người dùng email, số điện thoại… sẽ được lưu trữ dưới dạng chuỗi đã đc serialized trong cơ sở dữ liệu. Những dữ liệu này sẽ nhận được từ hàm get_metadata() hàm này được định nghĩa tại file wp-includes/meta.php ở dòng 292 – 297

if ( isset($meta_cache[$meta_key]) ) {
    if ( $single )
        return maybe_unserialize( $meta_cache[$meta_key][0] );
    else
        return array_map('maybe_unserialize', $meta_cache[$meta_key]);
}

Hàm get_metadata() gọi hàm maybe_unserialize() về cơ bản hàm này sẽ nhận dữ liệu truyền vào từ database và kiểm tra dữ liệu đó đã được serialize hay chưa, nếu có dữ liệu sẽ được unserialize(), ngược lại sẽ trả về dữ liệu nguyên gốc. hàm này được định nghĩa tại wp-includes/functions.php tại dòng 230 – 234

function maybe_unserialize( $original ) {
    if ( is_serialized( $original ) ) 
        return @unserialize( $original );
    return $original;
}

Lại tiếp tục phân tich sâu hơn vào hàm is_serialized() hàm này chỉ có nhiệm vụ check data đầu vào đã đc serialized chưa, nếu có thì trả về true ngược lại là false.
hàm này cũng được định nghĩa tại wp-includes/functions.php tại dòng 247-276:

function is_serialized( $data ) {
    // if it isn't a string, it isn't serialized
    if ( ! is_string( $data ) )
        return false;
    $data = trim( $data );
     if ( 'N;' == $data )
        return true;
    $length = strlen( $data );
    if ( $length < 4 )
        return false;
    if ( ':' !== $data[1] )
        return false;
    $lastc = $data[$length-1];
    if ( ';' !== $lastc && '}' !== $lastc )
        return false;
    $token = $data[0];
    switch ( $token ) {
        case 's' :
            if ( '"' !== $data[$length-2] )
                return false;
        case 'a' :
        case 'O' :
            return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data );
        case 'b' :
        case 'i' :
        case 'd' :
            return (bool) preg_match( "/^{$token}:[0-9.E-]+;\$/", $data );
    }
    return false;
}

Bây giờ bạn hãy thử tưởng tượng tên của bạn khi serialize sẽ là i:1 và đương nhiên khi unserialize nó sẽ trả lại giá trị 1. Nhưng nếu bạn thử với WordPress thì chuỗi trên sẽ là chuỗi unserialize.Nguyên nhân chính là do hàm update_metadata() tại file wp-includes/meta.php


// …
    $meta_value = wp_unslash($meta_value);
    $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
// …
    $meta_value = maybe_serialize( $meta_value );

    $data  = compact( 'meta_value' );
// …
    $wpdb->update( $table, $data, $where );
// …

Nếu đi sâu vào tìm hiểu hàm maybe_serialize()

function maybe_serialize( $data ) {
    if ( is_array( $data ) || is_object( $data ) )
        return serialize( $data );

    // Double serialization is required for backward compatibility.
    // See http://core.trac.wordpress.org/ticket/12930
    if ( is_serialized( $data ) )
        return serialize( $data );

    return $data;
}

Ta thấy rằng khi data là mảng hoặc object nó sẽ đc serialize và nếu là chuỗi đã được serialize() nó cũng được serialize. Vì thế nếu bạn đưa vào chuỗi i:1; thì đi qua hàm trên nó sẽ thành s:4:"i:1;";
Từ những phân tích trên ta thấy rằng để có thể chèn thành công một chuỗi serialize ta cần hàm is_serialized() trả về giá trị false khi chèn vào trong database và true khi data được load ra.
Có một điểm cần chú ý ở đây đó là nếu wordpress dùng MySQL với charset mặc định là utf-8, nó sẽ không thể lữu trữ được astral symbols(Untitled) đây là một ký tự đặc biệt có code point trong khoảng từ U+010000 tới U+10FFFF. Nếu như bạn cố tình lưu trữ ký tự này vào trong MySQL ví dụ chuỗi : fooUntitledbar thì MySQL sẽ loại bỏ từ Untitled trở về bên phải do đó chuỗi trên chỉ còn là  foo Từ điều này ta có thể tiến hành khai thác trên WordPress như sau: chúng ta sẽ thử truyền giá trị maybe_serialized('i:1;Untitled‘) WordPress sẽ không nghĩ rằng đây là chuỗi serialized vì phần kết thúc chuỗi là Untitled chứ không phải là ; hoặc } và sẽ lưu nó vào trong database. Tới phiên của MySQL nó chỉ có thể lưu là  i:1; và chúng ta đã chèn thành công chuỗi serialized vào trong database khi data được load ra i:1; hàm is_serialized() sẽ trả về true và tiến hành unserialized(). Dựa vào những phân tích của phần I chúng ta đã khai thác được lỗi PHP object injection.

Nguồn : http://tdsec.wordpress.com/2014/02/26/php-object-injection-tren-wordpress-phan-ii/

> Giải pháp bảo mật toàn diện dành cho website, đăng ký miễn phí 14-ngày tại đây <