간단하게 포트스캔 22, 80 이 열려있음
브라우저로 들어가면 메인페이지가 나오는데 소스코드, 등등 특이사항이 없었음. 한가지 정보는 download 로드 경로에 컴파일된 바이너리 몇개를 받을 수 있는데, 다운받아 실행해 본 결과 rot 47 로 치환암호하는 것 밖에 없었음.
항상 같은 레파토리지만. 디렉터리 탐색을 진행하면 admin 페이지가 존재함.
admin 페이지에 들어가서 개발자 도구를 보면 login.js, main.js, cookie.js 파일이 존재하는데 눈여겨보아야 할것은 저 login.js 파일임.
아래 소스코드는 login.js 소스코드임.
비동기통신이며 onload 를 통해 버튼을 누르는 submit 이벤트를 기다리고 있고, login 사설함수에서 querySelector 메서드로 로그인폼을 지정하고있으며 이 login 을 버튼을 누르게되면, submit 이벤트가 실행되어 헤더들을 달고 fetch 로 요청이 날라가는 postData 함수가 실행됨. 이 리턴된 response 값은 다시 login 함수로 전달되게 되는데, 이 response 값에 따라
세션을 설정하고있음. 만약 서버에서 리턴값이 invalid 가 오면 다시 admin 페이지에 출력되고 invalid 가 아니면 SessionToken 값으로 서버의 응답값을 설정하고 /admin 페이지로 리다이렉션시킴.
async function postData(url = '', data = {}) {
// Default options are marked with *
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *client
body: encodeFormData(data) // body data type must match "Content-Type" header
});
return response; // We don't always want JSON back
}
const encodeFormData = (data) => {
return Object.keys(data)
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
.join('&');
}
function onLoad() {
document.querySelector("#loginForm").addEventListener("submit", function (event) {
//on pressing enter
event.preventDefault()
login()
});
}
async function login() {
const usernameBox = document.querySelector("#username");
const passwordBox = document.querySelector("#password");
const loginStatus = document.querySelector("#loginStatus");
loginStatus.textContent = ""
const creds = { username: usernameBox.value, password: passwordBox.value }
const response = await postData("/api/login", creds)
const statusOrCookie = await response.text()
if (statusOrCookie === "Incorrect credentials") {
loginStatus.textContent = "Incorrect Credentials"
passwordBox.value=""
} else {
Cookies.set("SessionToken",statusOrCookie)
window.location = "/admin"
}
}
위의 소스코드를 증명해보면 아래와같음.
admin 로그인폼에서 존재하지않는 사용자 Credentials 을 요청을 보내고 burp 로 인터셉트해보면 INcorrect Credentials 이 응답 값으로 오는것을 볼 수 있음.
하지만, 동일하게 존재지않는 사용자 Credentials 요청을 보내어도, 서버로 부터 응답하는 response 값을 Incorrect credenatials 가 아닌 다른 문자열로 변조해주면, 다음 요청떄 SessionToken 값이 변조했던 값으로 설정시켜줄 수 있음.
이것이 가능한 이유는 서버로부터 응답한 값을 브라우저에서 렌더링하면서 js 가 실행되기 떄문에 가능한 것임.
SERVER ---> PROXY ---> CLIENT ---> 렌더링(js)
그리고 이 SessionToken 값을 admin 으로 바꾸어주면 james 의 ssh 개인키를 얻을 수 있음.
얻은 ssh 개인키로 james 로 로그인해보면 passphrase 를 입력하란 인풋창이 나오는데, 이것은 ssh2john 으로 john 포맷에 맞게 변경해준다음에 john 으로 크랙해주면 얻을 수 있음.
크랙으로 얻은 passphrase 은 ssh 로그인할때 입력해주면 james 로 로그인해줄수 있음. (여기서 user.txt 플래그를 찾을 수 있음.)
ps -aux --forest | grep -i root 로 루트 권한으로 실행되는 프로세스정보르들을 보면 백그라운드에서 실행되는 cron 서비스가 실행중임을확인 가능함.
cron 서비스의 설정파일인 /etc/crontab 파일을 보면, 1분마다 계속 curl 로 overpass.thm 에 요청을 보내고 있음.
여기서 바로 생각난 것은 /etc/hosts 파일을 변조시키는것임. /etc/hosts 파일은 ip 정보와 host 정보를 매핑시켜주는 설정파일임. 당연히 윈도우에도 있음. 여튼 아니나 다를까 /etc/hosts 파일이 root 가 아닌 other 사용자에게도 수정할 수 있는 권한이 있었음. (원래는 root 만 수정할 수 있는 파일이며 결국엔 잘못된 설정으로인한 취약점 (misconfiguration)) 임.
왼쪽이 변조전이고 오른쪽이 변조후인데, 로컬을 으미하는 127.0.0.1 의 호스트 이름을 overpass.htb.backup 으로 변조시키고 내 자신의 로컬서버 ip 주소를 overpass.thm 으로 변조시켰음.
cron 에서 매분 overpass.thm 에 요청을 요청을 보낸다고 했지만, 결국엔 /etc/hosts/ 파일을 변조시켰기 때문에 overpass.thm 에 요청을 보내면 내 자신의 서버에 요청을 보내는 것을 확인할 수있음. 지금은 로컬에 /downloads/src/buildscript.sh 파일이 없어서 404 가 발생하는것임.
이제 로컬에서 /downloads/src/ 경로를 만들어주고, buildscippt.sh 쉘스크립트에 리버스 쉘코드를 넣고 실행권한을 주고
서버를 띄운 후, 1분을 기다리면 원격지에서 cron 에 의해 스크립트가실행되며 root 쉘을 탈취할 수있음.