<흐름>
/4/ 인덱스 페이지 요청 -> 응답 코드 200, js코드로 login.php 리디렉션
-> 로그인 페이지 요청 -> 응답 코드 200, 로그인 페이지 보여줌
-> 로그인 정보 입력, 제출 -> 제출하면 /4/js/login.js 에서 입력값 받아서 onLogin 함수 실행
-> onLogin함수에서 입력값으로 url을 만들고 loginProc.php로 GET요청
-> loginProc.php에서 받은 인자들로 로그인 검증, 인정되면 login.js로 ok 신호 보냄
-> loginProc.php로 부터 받은 응답이 ok면 login.js가 사용자를 index.php로 리디렉션 (에러메시지는 숨김)
-> 응답이 ok가 아니면 login.js가 에러메시지 노출
<정석 풀이>
1) 돌돌로 로그인 후 로직을 파악하고 SQL Injection 가능성을 확인.
and '1'='1이거로 로그인 안되면 일단 로그아웃.
2) 로직 분석을 통해 클라이언트 측(js코드)에서 보낸 신호를 통해
인덱스 페이지로 리디렉션함을 확인.
3) 로그인 페이지에 입장한 뒤 버프 스위트의 intercept를 활성화.
admin / qwer(아무거나) 로 로그인.
4) 잡은 요청 데이터 화면을 우클릭하고 Do Intercept -> Response to this request 클릭
(응답 데이터 잡아서 수정하기 위함)
5) forward 한 번 눌러 요청 패킷을 서버에 보냄 -> 응답 데이터 잡아챔
6) 잡아챈 응답 데이터에서 fail -> ok 로 바꾸고 forward.
(응답데이터이기 때문에 화살표 <- 이 방향)
7) 인덱스 페이지 요청 데이터 forward. -> http history에서 응답 본문에 플래그 획득.
8) intercept 탭에 로그인 페이지 요청 데이터
-> 인덱스 페이지 상단에 스크립트 코드(리디렉션) 때문.
이때문에 브라우저에서는 admin으로 인덱스 페이지에 접근할 수 없음.
9) 브라우저에서도 인덱스 페이지에 접근하기 위해서는
인덱스 페이지 요청 패킷에서 다시 한 번 Do Intercept -> Response to this request 클릭
10) 요청 패킷 forward
-> 잡아챈 응답 패킷 본문에 스크립트 코드(리디렉션 부분) 삭제
11) 다시 forward 클릭 -> 브라우저에 나타난 인덱스 페이지 확인 (+플래그)
<중요한 포인트>
여기서 가장 중요하게 봐야하는 부분은 사용자 인증 신호를 자바스크립트로 보내고
사용자를 인덱스 페이지로 보내는 것 또한 자바스크립트가 담당한다는 점이다.
녹화된 노말틱님의 강의에서 어떤 스터디원 분이 이런 질문을 하셨다.
서버가 응답을 이미 보냈는데 버프 스윗에서 그걸 수정한다고 내가 받는 값이 달라질 수가 있나요?
와씨 내가 저걸 궁금해했어야 했구나, 하고 무릎을 탁! 쳤다. (응답을 수정할 수 있다는 생각도 모댓다..)
서버가 보낸 정보 중 클라이언트인 우리가 볼 수 있는 코드가 있고, 볼 수 없는 코드가 있다.
볼 수 있는 코드는 html과 같은 클라이언트 측 언어이고 볼 수 없는 코드는 php와 같은 서버 측 언어이다.
클라이언트 측 언어는 브라우저에서 처리되고
서버 측 언어는 서버에서 처리된다.
자바스크립트는 그 중 클라이언트 측 언어이다.
앞서 말했듯이 문제 사이트는 아래 두 과정을 자바스크립트로 처리한다.
1) 사용자에게 너 확인됐노라고 신호를 보냄.
(ok / fail)
2) ok 받으면 index.php로 리디렉션
브라우저의 자바스크립트가 fail값을 받기 전에 내가 ok로 수정하여 ok 값을 전달했고
그에 대한 결과인 location.href = "index.php"; 라는 코드를 유도할 수 있었던 것이다.
이 페이지는 심지어 로그인을 하지 않고도 인덱스 페이지로 접근할 수가 있다.
다만 index.php의 상단에 있는 로그인 페이지 리디렉션 코드가
돌돌로 접속할 때만 실행되지 않게끔 처리했던 것이다.
단순히 그뿐이라면 내가 얻어걸린 풀이 방법이 가능하지 않았을 것 같다.
(나는 index.php?userId=admin 방식으로 플래그를 얻었다.)
정확한 확인을 위해 이 사이트를 내 서버에서 구현해보려 한다.
<내 서버에서 구현하기>
1) login.php
POST 방식으로 js/login.js에 로그인 정보를 보내고 있다.
입장 시 세션 아이디를 발급하므로 세션을 활성화시켜야 한다.
<?php
session_start();
?>
<!-- HTML code for Bootstrap framework and form design -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/signin.css">
<title>Sign in</title>
</head>
<body>
<div class="container">
<form action="" method="post" name="Login_Form" class="form-signin">
<h2 class="form-signin-heading">Login In</h2>
<label for="inputUsername" class="sr-only">Username</label>
<input name="UserId" type="text" id="inputUserid" class="form-control" placeholder="User ID" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input name="Password" type="password" id="inputPassword" class="form-control" placeholder="Password" required>
<div class="checkbox">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
<button name="Submit" value="Login" class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
<div id="errorMsg" class="alert alert-danger alert-dismissible hidden" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<strong>Warning!</strong> Incorrect information.
</div>
</form>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="js/bootstrap.min.js"></script>
<script src="js/login.js"></script>
</body>
</html>
2) js/login.js
로그인 데이터를 받아 loginProc.php에 GET방식으로 데이터를 보내고
받은 결과를 통해 인덱스 페이지로 리디렉션할지, 경고문을 출력할지 결정하고 있다.
const HIDDEN_CLASS = "hidden";
const loginForm = document.querySelector("form");
function onLogin(e) {
e.preventDefault();
const userId = loginForm.querySelector("#inputUserid").value;
const userPw = loginForm.querySelector("#inputPassword").value;
const url = `/test/loginProc.php?userId=${userId}&userPw=${userPw}`;
fetch(url)
.then((response)=>response.json())
.then((data)=> {
const resultData = data.result;
console.log(resultData);
if (resultData == "ok") {
const errorMessage = document.querySelector("#errorMsg");
errorMessage.classList.add(HIDDEN_CLASS);
location.href = "admin_is_mine.php";
} else {
const errorMessage = document.querySelector("#errorMsg");
errorMessage.classList.remove(HIDDEN_CLASS);
}
})
}
loginForm.addEventListener("submit",onLogin);
3) loginProc.php
요청헤더에서 받은 데이터와 플래그 값을 세션에 저장하고
저장한 아이디와 비번이 doldol / dol1234일 경우 json 방식으로 js 파일에 결과값을 전달하고 있다.
<?php
session_start();
$_SESSION['userId'] = $_REQUEST['userId'];
$_SESSION['userPw'] = $_REQUEST['userPw'];
$_SESSION['flag'] = "segfault{플래그~~}";
header('Content-Type: application/json');
if ($_SESSION['userId'] == "doldol" && $_SESSION['userPw'] == "dol1234") {
echo json_encode(["result" => "ok"]);
} else {
echo json_encode(["result" => "fail"]);
}
?>
4) index.php
세션으로 받은 아이디가 doldol이 아니면 로그인 페이지로 리디렉션.
돌돌이 아니어도 페이지 본문을 볼 수 있게 하기 위해 else구문은 사용하지 않았다.
세션 데이터에 저장된 아이디에 따라 계정명을 출력하고, admin일 경우 플래그가 노출되도록 하였다.
<?php
session_start();
if($_SESSION['userId'] !== "doldol") {
echo "<script>location.href='admin_login.php';</script>";
} ?>
<!-- Show password protected content down here -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/stylesheet.css">
<title>Logged in</title>
</head>
<body>
<div class="container">
<div class="header clearfix">
<nav>
<ul class="nav nav-pills pull-right">
<li role="presentation" class="active"><a href="#">Home</a></li>
<li role="presentation"><a href="#">About</a></li>
<li role="presentation"><a href="#">Contact</a></li>
</ul>
</nav>
<h3 class="text-muted">Segfault</h3>
</div>
<div class="jumbotron">
<h1>Logged In</h1>
<p class="lead">User Name : <?=isset($_SESSION['userId'])?$_SESSION['userId']:"";?></p>
<?=$_SESSION['userId']=="admin"?$_SESSION['flag']:"" ;?>
<p><a class="btn btn-lg btn-success" href="logout.php" role="button">Log out</a></p>
</div>
<div class="row marketing">
</div>
<footer class="footer">
<p>© Segfault</p>
</footer>
</div>
<script src="../../assets/js/ie10-viewport-bug-workaround.js"></script>
</body>
</html>
이렇게 했을 때 구현된 부분이 있고, 되지 않은 부분이 있었다.
- 구현 된 부분
1) loginProc.php?userId=admin&userPw=qwer에 대한 응답이 자바스크립트로 돌아오고
그 값을 ok로 수정할 수 있었다.
2) ok로 수정할 경우 또 다시 로그인 페이지로 리디렉션 되었고 이 응답도 intercept하여 수정할 수 있었다.
3) 수정한 응답을 받으므로써 브라우저에서 admin계정 화면과 플래그값을 포함한
인덱스 페이지를 확인할 수 있었다.
- 구현되지 않은 부분
1) 인덱스 페이지에서 계정을 변경할 수 없었다. (index.php?userId=admin)
사실 이 부분은 인덱스 페이지에서 GET이나 REQUEST를 이용하여 세션 데이터를 수정하도록 하면
구현이야 가능하다.
예를 들어 인덱스 파일 상단 php코드에 아래 코드를 추가한다.
if(isset($_REQUEST['userId'])) {
$userId = $_REQUEST['userId'];
$_SESSION['userId'] = $userId;
}
이렇게 하면 리피터에서 index.php?userId=admin을 요구할 때 원하는 응답 본문을 얻을 수 있다.
(물론 브라우저에선 location.href 코드에 대한 조건 때문에 확인할 수 없다.)
풀리지 않는 의문은 저 코드가 왜 인덱스 페이지에 필요했느냐이다.
url로 간편하게 프로그래밍 결과를 확인하기 위해 추가하셨던 걸까.. 아니면 역시 저 코드가 아니라
다른 부분에서 문제가 있었던 걸까..
제대로 추측하기에 내 내공은 한참 부족하다. 그래서 당사자에게 여쭤보았다.
< ADMIN IS MINE 출제자에게 인덱스 페이지 확인 받기 >
결론부터 밝히자면 그런...코드를 쓰지 않으신 듯하다.
게다가 지금 내가 성공했던 방법으로 다시 해보면 플래그를 볼 수 없다. 계정도 doldol 그대로이다.
그래서 노말틱님께 혹시 코드 수정하셨느냐 여쭸더니 수정하지 않았다는 답을 주셨다.
수정하지 않았으면... 돼야하는데...? 근데 안된다.
귀신이 곡할 노릇이다.
1) 내가 사실 응답 코드를 수정하는 방법을 풀었던 것 아니냐?
아니다. 난 응답 데이터를 수정할 수 있을 거라는 생각을 못했다. (젠장)
2) 캐시가 남아 있었던 것 아니냐?
캐시가 남아있다고 한들 인덱스에서 계정을 바꿀수가 있단 말인가..?
3) 자바스크립트 코드가..
뭐가 됐든 이유가 있으면 코드를 수정하지 않은 한 다시 플래그가 보여야 한다.
하지만 지금은 이 방법으론 플래그를 볼 수 없다.
코드를 수정하신 것도 아니니 코드가 이유는 아닐 것 같다.
챗 지피티에 현재 상황을 정리해서 이유를 물어보기도 했다.
챗 지피티가 꼽은 문제 원인은 서버 측 세션관리나 서버 환경, 코드의 허점 등이 있다.
세션 관리는 저 문제를 풀 당시 사이트 사용자 처리가 엉켜서 그럴 수 있을 거라는 내용이고
서버 환경 또한 비슷한 내용이다. 그때는 서버 환경이 불안정했을 수 있다는 내용이다.
코드의 허점은... 내가 지금껏 여기에 뭔가 있겠거니 찾았던 부분인데 잘 모르겠다...!!!!
서버 환경의 문제였을까?
내 서버는 불안정할 일이 없어서 확인할 수가 없다.
.
.
.
곡~
고옥~
곡~
귀신이 곡하는 소리다. 왓 어 미스테리어스.....
아무래도 이 문제는 미래의 나에게 맡겨야할 것 같다..분하다...
이 글을 읽으신 분께서는 의심가는 부분이 있으시다면 부디 알려주셨으면 좋겠습니다..감사합니다..
'자습' 카테고리의 다른 글
웹 개발: 마이페이지 (2) | 2024.11.25 |
---|---|
웹 개발: 게시판 - 글 작성 (2) | 2024.11.13 |
내가 만든 사이트에 세션, 로그아웃, DB 연동 파일 분리 적용하기 (1) | 2024.11.12 |