Index
CSP (Content Security Policy)
CSP는 XSS 및 Injection 공격을 탐지하고 완화하기 위해 도움을 주는 보안 계층이다. HTTP 헤더를 기반으로 정책을 추가하는 방식이다.
해당 low 레벨 문제에서는 특정 URL을 받아 include하는 폼이 있다.
low
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com www.toptal.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, hastebin.com, jquery and google analytics.
header($headerCSP);
# These might work if you can't create your own for some reason
# https://pastebin.com/raw/R570EE00
# https://www.toptal.com/developers/hastebin/raw/cezaruzeka
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
<script src='" . $_POST['include'] . "'></script>
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>You can include scripts from external sources, examine the Content Security Policy and enter a URL to include here:</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
';
header에서 self와 pastbin.com 등 특정 사이트의 js를 허용한다.
include의 입력 태그를 POST로 받아서 <script> 구문 안으로 넣어 XSS가 발생한다.
pasbin.com에서 스크립트 구문을 작성하고, 이 주소를 입력 폼에 넣어주자.
하지만, 경고창을 띄울 수 없었다. 그래서 CSP에 self도 포함하기 때문에 로컬 경로에 파일을 세팅하고 진행했다.
/tests/csp_low 파일에 alert(document.cookie); 를 입력하고 경로를 include 하면 XSS가 발생한다.
medium
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";
header($headerCSP);
// Disable XSS protections so that inline alert boxes will work
header ("X-XSS-Protection: 0");
# <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>Whatever you enter here gets dropped directly into the page, see if you can get an alert box to pop up.</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
';
이번 헤더에서는 unsafe-inline 옵션이 들어가 있다. 이 옵션은 인라인 스크립트를 사용할 수 있게 한다. 뒤에 nonce 값을 통해 script에 nonce 값을 넣어줘야 스크립트를 사용할 수 있다. 이 문제에서는 nonce 값이 고정이기 때문에 손쉽게 XSS를 발생시킬 수 있다.
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(document.cookie)</script>
high
문제의 폼이 바뀌었다. 소스코드를 분석해보자.
<?php
$headerCSP = "Content-Security-Policy: script-src 'self';";
header($headerCSP);
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p>
<p>1+2+3+4+5=<span id="answer"></span></p>
<input type="button" id="solve" value="Solve the sum" />
</form>
<script src="source/high.js"></script>
';
solve the sum 버튼을 누르면 앞에 수식의 결과가 나온다. 코드 아래 high.js를 참조하고 있으니, 이 파일을 살펴본다.
source/high.js
function clickButton() {
var s = document.createElement("script");
s.src = "source/jsonp.php?callback=solveSum";
document.body.appendChild(s);
}
function solveSum(obj) {
if ("answer" in obj) {
document.getElementById("answer").innerHTML = obj['answer'];
}
}
var solve_button = document.getElementById ("solve");
if (solve_button) {
solve_button.addEventListener("click", function() {
clickButton();
});
}
버튼의 id가 solve로 해놓았기 때문에 이를 통해 요소를 가져온다. addEventListener로 클릭했을 때, clickButton 함수가 호출된다. jsonp.php로 GET 방식으로 callback 인자를 전달한다.
source/jsonp.php
<?php
header("Content-Type: application/json; charset=UTF-8");
if (array_key_exists ("callback", $_GET)) {
$callback = $_GET['callback'];
} else {
return "";
}
$outp = array ("answer" => "15");
echo $callback . "(".json_encode($outp).")";
?>
callback에 대한 검증이 따로 없다. 스크립트 구문을 넣으면 될 것 같다.
프록시 도구를 이용하여 패킷을 잡고, jsonp.php로 넘어가는 callback 인자를 변조한다.
원래는 함수 이름을 넘겨서 실행시키는 callback인자이므로, <script>를 감싸지 않고 바로 alert를 띄우면 된다.
impossible
<?php
<!-- high와 코드 동일 -->
<script src="source/impossible.js"></script>
';
참조하는 js파일만 다르다.
source/impossible.js
function clickButton() {
var s = document.createElement("script");
s.src = "source/jsonp_impossible.php";
document.body.appendChild(s);
}
function solveSum(obj) {
if ("answer" in obj) {
document.getElementById("answer").innerHTML = obj['answer'];
}
}
var solve_button = document.getElementById ("solve");
if (solve_button) {
solve_button.addEventListener("click", function() {
clickButton();
});
}
high와는 다르게 callback 파라미터를 GET방식으로 넘기지 않는다.
source/jsonp_impossible.php
<?php
header("Content-Type: application/json; charset=UTF-8");
$outp = array ("answer" => "15");
echo "solveSum (".json_encode($outp).")";
?>
php 파일을 확인해보면, callback을 사용하는게 아닌 solveSum을 하드코딩으로 호출해버린다. 그래서 high 난이도의 문제의 취약점을 해결했다.
Uploaded by N2T