2번 문제는 먼저 소스 코드 분석으로 시작합니다.

 

소스 코드의 맨 마지막 줄에 Hint가 적혀있습니다.

Hint : Join / id = pw

 

Join 버튼의 Html 코드를 보면 onclick="chk_form()" 을 통해 script 를 호출합니다.

 

 

 

 

id 와 pw 가 같을 때 로그인 실패 alert 이 뜨고, 이 외에 submit() 을 보내는 걸 확인할 수 있습니다. 

 

힌트가 id = pw 인데, 함수는 아니다? 

여기서 FiddlerBurp Suite 를 이용하여 값을 변경해 보낼 수 있다는 사실을 기억해야 합니다.

 

원래 Fiddler 를 더 좋아했으나 유료인만큼 이번엔 Burp Suite 를 사용한 방법을 적겠습니다.

 

 

버프 스위트 기본적인 설정은 완료했다는 가정 하에 문제 풀이를 시작합니다.

 

 

 

Proxy -> Intercept -> Open browser 로 브라우저를 띄워줍니다.

 

URL에 써니나타스 2번 문제 URL를 붙여넣습니다. 그 다음 버프 스위트에서 Intercept is off 를 눌러 on 으로 바꿉니다.

 

우선 submit()을 받아야 하기 때문에 id, pw 가 다르도록 아무 글자나 적어 Join 를 누르면 아래와 같이 보입니다.

 

 

 

 

 

Action -> Send to Repeater 를 클릭하고 메뉴의 Reqeater 로 이동합니다.

 

왼쪽의 Request 에서 idpw동일하게 변경한 후 Send클릭하면 Response 에서 Authkey 를 확인할 수 있습니다.

 

 

 

 

 

 

 

 

정답

더보기

Burp Suite 를 이용해 id = pw 를 동일하게 만들어 요청한다.

 

 

 

'Security > Web Hacking' 카테고리의 다른 글

SuNiNaTaS(써니나타스) Challenges 1번 문제 풀이  (0) 2023.03.21
PHP SQL Injection 방어 기법  (0) 2022.09.01
Time-Based/Union-Based SQL Injection  (0) 2022.08.31
Blind SQL Injection  (0) 2022.08.30
SQL Injection  (0) 2022.08.30

 

 

1번 문제의 <소스 코드 보기>를 확인하면 .asp 파일이라는 것을 확인할 수 있습니다.

 

소스 코드를 보면 어떠한 str 값이 함수를 거쳐 최종 result 값이 admin이 될 때 pw를 알 수 있습니다.

 

위에서 사용되는 Replace()Mid() 함수를 안다면 쉽게 풀리는 문제입니다.

 

 

Replace() - 문자열 치환

Mid() - 문자열 자르기

 

 

result = Replace(str, "a", "aad")
result = Replace(result,"i","in")

aaad치환

iin 으로 치환

 

 

result1 = Mid(result,2,2)
result2 = Mid(result,4,6)

result 의 2 번째부터 자른 후 2 개의 문자열을 가져와 result1 에 대입

result 의 4 번째부터 자른 후 6 개의 문자열을 가져와 result2 에 대입

 

 

result = result1 & result2

result1 과 result2 를 합쳐 result 에 저장

 

 

한 값이 admin이 된다면 pw를 알 수 있게 됩니다.

 

 

 

str = ai

result = aadi

result = aadin

result1 = ad

result2 =  in

result = adin

 

 

주어진 값으로만 문자열을 만져보면 m이 빠져있다는 것을 확인할 수 있습니다. 다시 처음부터 m을 넣고 시작하면

 

 

str = ami

result = aadmi

result = aadmin

result1 = ad

result2 = min

result = admin

 

이 완성됩니다.

 

 

 

정답

 

 

 

'Security > Web Hacking' 카테고리의 다른 글

SuNiNaTaS(써니나타스) Challenges 2번 문제 풀이  (0) 2023.03.21
PHP SQL Injection 방어 기법  (0) 2022.09.01
Time-Based/Union-Based SQL Injection  (0) 2022.08.31
Blind SQL Injection  (0) 2022.08.30
SQL Injection  (0) 2022.08.30

 

 

코드를 볼 수 있다면 그 코드를 보고 어떤 방식으로 공격을 수행해야하는지 알 수 있다. 그러기 위해서는 SQL Injection 방어 기법에 대해 아는 것도 중요하다.

 

[ 특수문자 필터링 ]

쿼리에서 싱글 쿼터의 역할은 문자열의 범위를 지정하는 것이다. 그렇다면 사용자가 입력하는 싱글 쿼터를 일반 문자로서 인식하게 만들면 된다.

PHP에는 사용자가 입력한 특수 문자가 쿼리에서의 역할이 있는 경우, 이를 수행하지 않고 일반 문자로 바꾸어 주는 addslashes() 함수가 존재한다. 싱글 쿼터(')를 포함하여 더블 쿼터("), 어퍼스트로피(`), NULL 문자 앞에 역슬래시(\)를 붙인다.

MySQL은 특수 문자 앞에 역슬래시(\)가 붙으면 일반 문자로 취급한다.

 

GET이나 POST로 전달받은 파라미터를 저장하는 구문 앞에 함수를 붙여 사용할 수 있다.

<?php
	$id = addslashes($_GET['id_param']);
	$pw = addslashes($_GET['pw_param']);

 

 

 

[ Prepared Statement 구문 사용 ]

Prepared Statement는 사용할 쿼리를 준비해 두고 변화가 필요한 부분만 교체하는 방법으로 데이터베이스에서 쿼리를 실행시키는 방식이다. 동일한 구조의 쿼리에서 조건만 바꾸면 되는 기능을 사용하기 위해 매번 새로운 쿼리를 만들고 실행하는 점이 비효율적임을 인지하고 보완하기 위해 만들어진 기능이다.

 

계속 변화가 생기는 사용자가 입력한 값이 들어가야 하는 부분을 ? 로 작성하여 변수에 대입한다.

$query = "select * from user where id=? and pw=?";
$stmt = mysqli_prepare($db_conn, $query);

mysqli_prepare() 함수를 이용하여 쿼리를 실행시킬 데이터베이스($db_conn)와 준비할 쿼리($query)를 인자로 넣어준다.

 

$bind = mysqli_stmt_bind_param($stmt, "ss", $id, $pw);

준비된 쿼리에 사용자의 입력 값을 대입하는 바인딩(Binding) 과정이 필요하다.

mysqli_stmt_bind_param() 함수의 인자에는 순서대로 준비된 쿼리, 입력 값의 타입, 각 대입할 변수가 들어간다. 여기서는 id와 pw 모두 문자열이기 때문에 string의 앞글자를 따 ss를 넣어주었고, 만약 대입될 변수의 입력 값이 문자, 숫자, 문자 순이라면 sis를 넣어주면 된다.

 

위 코드처럼 Prepared Statement는 사용자로부터 입력 받는 데이터가 어떤 형태인지 미리 알고 있기 때문에 개발자가 의도한 범위를 벗어난 입력 값을 입력할 수 없게 되어 SQL Injection의 방어 기법으로도 쓰일 수 있는 것이다.

 

 

 

[ Time-Based ]

Time-Based SQL Injection 공격은 sleep() 함수를 사용하여 쿼리의 조건이 참일 경우 응답 시간이 지연되고, 거짓이라면 즉시 결과를 반환하여 응답 지연 시간을 통해 구문의 참과 거짓을 알아낼 수 있다.

sleep() 함수를 제외하고도 benchmark(), waitfor() 함수의 사용이 가능하다.

 

admin 계정의 pw가 admin123인 것을 알고 있다는 가정 하에 실습을 진행한다.

' or ascii(substring(pw,1,1))=97 and sleep(5)--(공백)

위 구문을 실행하면 5초의 지연 후 페이지가 응답되는 것을 볼 수 있다.

만약 데이터베이스에 없는 일치하지 않는 값을 입력한다면? 페이지의 지연 없이 바로 응답될 것이다.

 

 

[ Union-Based ]

Union SQL Injection 은 기존의 쿼리에 UNION 연산자를 이용하여 2개 이상의 쿼리를 요청하며 공격하는 방법이다. 컬럼의 개수와 데이터 형식이 같아야 한다는 조건이 붙는다.

' union select 1--(공백)
=> 실패
' union select 1,2--(공백)
=> 성공

' union select 1,2,3--(공백)
=> 실패

 

위 공격을 통해 컬럼의 개수는 2개인 것을 알 수 있다. 이를 이용해 현재 데이터베이스를 사용 중인 사용자의 정보도 알아낼 수 있다.

 

' union select user(),2--(공백)

 

 

 

 

 

 

일반 SQL Injection은 원하는 정보를 한 번에 알아낼 수 있다. 하지만 원하는 정보를 한 번에 얻지 못할 때, 서버에서 주는 참과 거짓의 응답만으로 데이터를 유출해내는 기법을 Blind SQL Injection 이라고 한다.

 

[ 얻고자 하는 정보의 길이 ]

얻고자 하는 정보의 길이를 알게 되면 효율적인 공격이 가능해지기 때문에 length() 함수를 사용해 데이터의 길이를 알아낼 수 있다.

아래는 pw 컬럼에 들어 있는 데이터의 길이를 확인하기 위한 payload이다.

a' or length(pw)<10-- 

-- 뒤 공백은 필수이다. pw가 10자 보다 짧은 계정이 있다면 로그인이 성공하고, 만약 거짓이라면 로그인 실패가 뜨게 된다. 이런식으로 참과 거짓을 알려주는 서버의 응답을 통해 데이터를 유추해낼 수 있다.

pw가 10보다 짧은 계정이 존재한다. 계속해서 숫자를 줄여나가며 공격을 시도해보면 length(pw)<8 에서 로그인 실패가 뜬다.

 

이를 통해 guest 계정의 비밀번호는 8자리인 것을 알 수 있게 된다. 그렇다면 admin 계정의 비밀번호 길이를 알고 싶다면 어떻게 해야할까?

이때는 AND 구문을 이용해주면 된다. 아래와 같은 payload를 작성하면 id가 admin일 때의 경우에만 조건을 걸 수 있다.

a' or length(pw)<10 and id='admin'-- 

 

 

[ 실제 데이터의 값 대입 ]

위 payload를 통해 admin 계정의 pw 길이 또한 8인 것을 알아냈다고 가정한다. 길이를 알아낸 후에는 실제 데이터 값을 알아내야 한다.

이때 사용할 수 있는 함수가 substring() 이다. 문자열을 조각내는 함수로 첫 번째 인자는 자르고자 하는 대상, 두 번째 인자는 몇 번째부터 자를지, 세 번째 인자는 몇 글자를 자를지 지정할 수 있다.

 

우선은 실습을 위한 것이기 때문에 admin 계정의 비밀번호가 admin123이라는 것을 알고 시작하도록 한다. MySQL은 대문자와 소문자를 구별하지 못하기 때문에 문자의 대입은 아스키코드를 사용하는 것이 정확한 결과를 얻을 수 있다.

 

아래 payload는 pw 칼럼에 존재하는 데이터의 첫 번째 글자부터 한 글자를 자르고 그 값이 소문자 a와 일치하는지 확인하는 구문이다.

a' or ascii(substring(pw,1,1))=97 and id='admin'--(공백)

로그인에 성공한 모습을 보아, admin 계정의 pw 첫 글자는 'a' 라는 것을 알 수 있다. 위 방법을 이용하면 데이터 값을 알아낼 수 있게 된다.

 

다음 포스팅은 Time Based, Union Based SQL Injection에 대해 설명한다.

 

 

 

SQL Injection 이란 'SQL에 악의적인 쿼리를 삽입하여 실행되게 만드는 것' 이라고 정의한다.

실습은 SQL Injection에 취약하게 직접 만든 로그인 페이지에서 진행한다.

[ 계정 ID를 알고 있을 경우 ]

먼저 관리자 계정의 ID는 admin이다. ID에 아래와 같은 payload를 작성한다.

admin'-- 

정확히는 admin'-- 을 한 후 공백 한 칸을 주어야한다. MySQL에서 -(하이픈) 두 개와 공백은 주석을 의미하기 때문이다.

비밀번호는 아무거나 입력 후 로그인을 시도한다.

admin 계정으로 로그인에 성공한 모습이다.

데이터베이스에 만들어둔 admin 계정의 비밀번호는 admin123 이었다. 하지만 어떻게 로그인이 성공할 수 있었을까?

개발자는 사용자가 입력할 수 있는 범위를 문자열에만 한정되는 코드를 작성했다. 하지만 공격자가 '(싱글쿼터)를 직접 넣어주어 'admin' 에서 문자열이 종료될 수 있게 만들어주고, -(하이픈) 두 개와 공백을 이용하여 password의 입력 쿼리는 주석 처리를 해버린 것이다. 결국 select * from user where id = 'admin' 까지만 쿼리에 들어가게 되고, ID가 admin인 계정의 비밀번호 없이도 admin에 접근할 수 있게 되었다.

 

 

[ ID와 PW 모두 모를 경우 ]

이번엔 PW에 아래 payload를 작성한다.

1' or '1' = '1

아까와 달리 guest 계정으로 로그인이 성공한 것을 볼 수 있다. 주석을 사용하지 않고 '(싱글쿼터)의 개수를 맞춰준 공격 구문이다.

쿼리에서 AND 와 OR 중 우선순위가 높은 것은 AND 이기 때문에 where절 뒤에 id = 'abc' and pw = '1' 이 먼저 수행된다. 하지만 user 테이블에 ID가 abc, PW가 1인 데이터는 없기 때문에 거짓이 된다. 그러나 둘 중 하나만 참이어도 참으로 인식하는 OR 뒤에 '1' = '1' 이라는 참이 되는 조건이 수행되어 결국 구문 전체가 참으로 인식된 채 데이터를 조회할 수 있게 된 것이다.

 

하지만 admin이 아닌 guest로 로그인된 이유는 mysqli_fetch_array() 함수를 이용했기 때문이다. 이 함수는 모든 데이터 중 가장 위에 있는 행을 가져오는데, 맨 위에 있는 계정이 guest 라는 단순한 이유에서이다.

 

간단하게 SQL Injection의 원리를 알아봤다. 다음은 Blind SQL Injection에 대해 포스팅한다.

 

+ Recent posts