Basic server-side template injection

server-side template injection이다. 이번 랩은 ERB 템플릿을 사용했다고 한다. morale.txt를 없애보자.

상품의 details를 보려고 했더니 재고가 없다고 한다.

해당 요청을 확인해보았다. parameter로 message를 받고 있다.

위의 페이지를 참고하여 ERB 템플릿에서 어떤 형식으로 써야 일반적인 string으로 처리되지 않고 원하는 결과를 수행하는지 알아냈다. <%= %> 형식으로 써주면 될 듯하다.

<%=7*7%>을 먼저 해봤다. 49가 잘 전달된 것을 확인할 수 있었다.

<%=system(”ls”)%>도 해보았다. ls가 수행되어 morale.txt가 있는 것을 확인했다!

마지막으로 <%=rm+/home/carlos/morale.txt%>를 해줌으로 morale.txt를 지웠다.

성공~
Basic server-side template injection (code context)

이번에는 Tornado template을 사용한다고 한다. Tornado 템플릿은 python 기반의 템플릿이다. 문제를 보자.

로그인을 하면 이메일을 업데이트하는 기능과 이름이 어떻게 보일지 정하는 기능이 있다.

Preferred name을 first name으로 하고서 댓글을 달았더니 Peter로 나왔다.

Preferred name을 변경하는 request는 위와 같았다. POST 요청으로 아래에 user.??를 바꾸면 된다.

이번에는 first_name을 그냥 name으로 바꿔서 보내봤다.

그랬더니 Peter Wiener로 전체 이름이 나오는 것을 확인할 수 있었다!
우리가 공략할 부분은 저 user.name이 들어가는 부분이다. 해당 부분에 python 코드를 삽입하면 된다.

Tornado 에서는 {{ ~~ }} 형태로 넣으면 우리가 작성한 코드를 실행할 수 있다고 한다.

기본적으로 {{이 들어가기에 user.name을 설정해주고 }}을 닫아준다. 그 다음에 뒤에 {{을 열어서 내가 원하는 코드를 넣어주었다. 먼저 7*7을 넣어봤다.

49가 같이 출력되는 것을 확인할 수 있었다.

그다음은 whoami를 실행하도록 코드를 넣었다. python이기에 os를 먼저 import 해주고 os.system으로 whoami를 실행했다.

carlos까지 함께 출력되는 것을 확인할 수 있었다!

마지막으로 morale.txt를 rm으로 없애주면,

성공!
Server-side template injection using documentation

이번 문제는 템플릿 엔진을 알아내고 morale.txt를 삭제하면 된다. 로그인 정보를 주고 있다.

로그인했다.

상품 정보에 들어가보니 템플릿을 수정할 수 있는 기능이 있다!

해당 기능을 통해 템플릿을 수정하고 이를 확인해볼 수 있다. 템플릿 맨 아랫줄에 공격할 만한 부분이 보인다.
<p>Hurry! Only ${product.stock} left of ${product.name} at ${product.price}.</p>
${~}를 통해 상품의 재고와 이름, 가격을 가져오고 있다.

위와 같이 표시되고 있다.

product.stock을 다른 옵션으로 바꿔보았다. 그랬더니 에러메세지를 출력한다.
에러 메세지를 통해 해당 템플릿이 FreeMarker를 통해 만들어졌음을 확인할 수 있었다!

FreeMarker는 java를 기반으로 한 템플릿이었다. 따라서 ${~} 형식으로 코드를 실행할 수 있었다.
java에 대해 잘 알지는 모르지만 예시에서 보여주는 것을 참고해서 명령어를 실행하는 코드를 적어보았다.

id를 정상적으로 실행하고 있다!

마찬가지로 ls 역시 잘 실행하고 있다.

마지막으로 morale.txt를 삭제해주면,

성공~
Server-side template injection in an unknown language with a documented exploit

이번에도 템플릿 엔진을 알아낸 후 morale.txt를 없애라고 한다!

첫 번째 상품의 상세 정보를 보려고 하니 재고가 없다고 한다. 해당 GET 메시지를 얻어서 확인했다.

템플릿 엔진이 뭔지 모르기에 {{7*7}}을 넣어보았다. 그랬더니 에러 메시지가 출력되었고, 이를 통해 템플릿 엔진이 Handlebars임을 알아냈다.

Handlebars는 NodeJS를 기반으로 한 템플릿 엔진이었다.

위 홈페이지에서 얻은 예시 메시지를 바탕으로 exec 함수에 rm /home/carlos/morale.txt를 넣어줬다. 그리고 URL encodeing된 버전으로 message에 넣어줬더니 성공적으로 삭제했다.

성공~
Server-side template injection with information disclosure via user-supplied objects

이번 랩에서 사용한 템플릿을 통해 민감한 정보에 접근할 수 있다고 한다. 문제를 보자.

이번에도 content-manager로 로그인한 후에 템플릿 수정 기능에 접근해 보았다. 위처럼 product.??가 출력되는 것을 확인할 수 있었다.

이상한 걸 입력해서 템플릿이 파이썬을 기반으로 하였음을 알아냈다.

위 페이지에서 python 기반으로 된 jinja2를 찾아냈다. 예시에서 보여주는 대로 debug와 함께 settings.SECRET_KEY를 입력해보았다.

입력했더니 다음과 같이 디버깅 정보가 나왔다!

맨 아래에 secret_key가 함께 출력된 것을 확인할 수 있었다.

복사해서 제출하면 성공~
Server-side template injection in a sandboxed environment

이번 문제는 Freemarker 엔진을 사용하고 있다고 한다. sandbox 환경이 취약하다고 하니 이를 이용해 my_password.txt를 읽어보자.

아까 페이지에서 봤던 payload를 하나씩 순서대로 집어넣어 보았다.

에러가 떴다.

또 에러

마찬가지로 에러

마지막 payload만 먹혔다! 아무래도 ascii 문자를 10진수로 하나씩 표현해준 것 같다.

chatgpt에 물어보니 대충 password.txt와 연결해서 데이터를 읽어오고 이를 띄어쓰기로 구분해서 출력해준 것 같다.

cyberchef로 돌려서 ascii를 알아냈다.

답에 입력해보니

성공~
Server-side template injection with a custom exploit

이번에는 custom exploit을 만들어서 .ssh의 id_rsa를 삭제하라고 한다. 문제를 보자.

예전에 있었던 문제와 같이 보여질 이름을 설정하는 기능과 아바타를 설정하는 기능이 모두 있었다. user.name 대신에 user.lasfs와 같이 이상하게 넣어봤더니 템플릿 엔진이 PHP 기반의 Twig로 돌아간다는 것을 확인할 수 있었다.

위 페이지에서 Twig의 payload를 찾아서 모두 넣어보았다.

다음과 같이 user.name}}으로 닫고 다 해봤는데 실패했다..
그래서 예에전에 File Vulnerabilty에서 풀었던 것처럼 아바타 이미지 업로드 기능에 shell.php를 업로드해보았다.
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$name = $_POST["name"];
echo "Hello, $name!";
}
?>
간단하게 만든 shell.php이다.

mime type이 image가 아니라며 오류를 출력했다! 이 오류메시지에서 user 객체의 setAvatar 함수를 사용한다는 사실을 알 수 있었다.

바로 user.setAvatar()를 blog-~~-display에 넣어보았다.

에러가 출력되었다. setAvatar 함수에 두개의 인자가 필요하다고 한다!

일단은 인자로 뭘 넣어야 할지 몰라서 /etc/passwd를 둘 다 넣어주었다.

또 에러 메세지! 두번째 인자에는 mime type이 들어가나보다.

따라서 두번째 인자를 image/jpeg로 바꿔주었다.
성공적으로 들어가서 내 댓글의 아바타 이미지 주소를 복사해서 확인해보았다.

avatar 이미지에서 /etc/passwd를 보여주고 있었다! 이 기능을 활용하면 id_rsa를 읽을 수 있을 것 같다.

경로를 /etc/passwd에서 /home/carlos/.ssh/id_rsa로 바꾸고 이미지 주소를 확인해보았다.

볼 게 없다고 한다. 다시 생각해보니 파일을 읽는 게 아니라 지워야 한다.
그런데 아바타 이미지를 설정해서 파일을 읽는 것만 가능하지, 지우는 것은 어떻게 해야할까?
고민하다가 위에서 setAvatar 함수 역시 User.php에서 나온 것을 보았던 것이 기억났다. 따라서 이번에는 User.php를 읽어보았다.

위와 같이 보내주었더니 User.php를 얻을 수 있었다.
<?php
class User {
public $username;
public $name;
public $first_name;
public $nickname;
public $user_dir;
public function __construct($username, $name, $first_name, $nickname) {
$this->username = $username;
$this->name = $name;
$this->first_name = $first_name;
$this->nickname = $nickname;
$this->user_dir = "users/" . $this->username;
$this->avatarLink = $this->user_dir . "/avatar";
if (!file_exists($this->user_dir)) {
if (!mkdir($this->user_dir, 0755, true))
{
throw new Exception("Could not mkdir users/" . $this->username);
}
}
}
public function setAvatar($filename, $mimetype) {
if (strpos($mimetype, "image/") !== 0) {
throw new Exception("Uploaded file mime type is not an image: " . $mimetype);
}
if (is_link($this->avatarLink)) {
$this->rm($this->avatarLink);
}
if (!symlink($filename, $this->avatarLink)) {
throw new Exception("Failed to write symlink " . $filename . " -> " . $this->avatarLink);
}
}
public function delete() {
$file = $this->user_dir . "/disabled";
if (file_put_contents($file, "") === false) {
throw new Exception("Could not write to " . $file);
}
}
public function gdprDelete() {
$this->rm(readlink($this->avatarLink));
$this->rm($this->avatarLink);
$this->delete();
}
private function rm($filename) {
if (!unlink($filename)) {
throw new Exception("Could not delete " . $filename);
}
}
}
?>
User.php는 위와 같다. 안에 보면 우리가 사용했던 setAvatar 함수도 있는 것을 확인할 수 있다.
그리고 아래에 있는 함수인 gdprDelete 함수에서 rm을 사용해서 파일을 삭제하고 있으므로 해당 함수를 사용하면 될 것 같다.

먼저 avatar 이미지를 다시 id_rsa로 바꿔주고

gdprDelete 함수로 다시 바꿔주면,

성공~