slide-image

웹 보안

[2019-1]

#02

 

 

이번에는 SQL 인젝션과 커멘드 인젝션에 대한 내용이었다.

 

SQL 인젝션이란, 데이터베이스의 개념을 알아야 할 수 있는 공격방식이다.

데이터 베이스에 전송되는 SQL 쿼리문을 조작하여, 데이터를 변조하거나 허가되지 않은 정보에 접근하는 공격으로 개인정보를 빼낼 때 많이 사용한다.(어나니머스 WTO 해킹)

 

이번에 튜터링한 기본적인 SQL 인젝션 공격 방법은 Where 구문 우회 공격과, UNION 공격이었다.

 

먼저, Where 구문 우회 공격을 살펴보았다.

일단 정상정인 웹 요청 SQL 쿼리문이라면, 클라이언트가 ID가 1인 회원을 조회하고 싶다고 서버 측에 요청을 할 경우 SQL 쿼리문의 형태는 SELECT name, email FROM users WHERE ID='1'가 될 것이다.

그러면 $id=$_REQUEST['id'];

$query="SELECT name, email FROM users WHERE id='$id';";

가 될텐데, 이렇게 사용자가 입력한 ID 파라미터 값이 쿼리문의 일부로 사용된다는 것을 이용한 공격법이다.

즉, 따옴표 가지고 장난치는 건데, 만약 사용자가 1' or 'a'='a라고 입력하면, 쿼리문은

SELECT name, email FROM users WHERE ID='1'에서

SELECT name, email FROM users WHERE ID='1' or 'a'='a'이 되어 쿼리문이 문법적으로는 아무런 문제가 없어진다.

하지만 저 쿼리문의 뜻은 'a' = 'a'가 항상 True이므로, 모든 DB를 조회할 수 있다.

 

또한 UNION구문을 활용할 수 있는데, UNION은 합집합이므로 만약 사용자가 a' UNION SELECT name, pw FROM users#라고 입력할 경우에 쿼리문은

SELECT name, email FROM users WHERE ID='1'에서

SELECT name, email FROM users WHERE ID='a' UNION SELECT name, pw FROM users#'가된다.

#는 주석 처리를 하는 것으로 맨 뒤의 따옴표를 무시하는 역할을 한다.

즉, SELECT name, email FROM users WHERE ID='a' UNION SELECT name, pw FROM users가 되는 것이다.

이번에도 쿼리문이 문법적으로 문제가 없으므로, 실행이 되고, 의미적으로는 id가 a인 사람과 users에서 name, pw를 출력하는 의미가 된다.

그렇지만, UNION에서 중요한 점은, sql 쿼리문에서 UNION 명령어는 두개의 테이블에서 한번에 데이터를 조회할 때 쓰이고, 앞 뒤 테이븡에서 조회하고자 하는 컬럼의 수가 동일해야 한다. 

만약 데이터베이스에 admin과 member라는 테이블이 두 개 있고, UNION을 이용해 이 둘 테이블을 조회(SELECT)하고 싶으면,

각각 두개의 테이블에서 하나의 애트리뷰트(컬럼, 속성)값을 조회하거나 2개씩 조회하거나 3개씩 조회하는 등 컬럼 수를 맞춰야 한다.

따라서 그 전에 ORDER MY라는 결과를 정렬해주는 키워드(쿼리의 일종)을 이용한다. 

 

실습이 더 이해가 잘 될 것임.

 

dvwa로 실습을 해보았다.

일단은 security level을 low로 설정한다.

일단 '을 입력해서 에러가 나면 sql인젝션을 쓸 수 있을 지 모른다는 생각을 해야한다.

쿼리문이 '''가 되어서 에러가 나는 것. 

 

그러면 맨 처음에 where 구문 우회 공격을 시도해보았다.

성공 :>

이제 UNION을 이용해 보려고 했는데, wehre id='1'일 때의 컬럼이 몇 개 반환 되는지 모르므로, 지정된 칼럼을 기준으로 결과를 정렬하는 order by를 이용해서 order by 1#부터 넣어보았다.

2#도 되고,

3#부터 에러가 남. 전체 칼럼의 개수보다 더 큰 값을 입력해서 에러가 발생하는 것이다. 그러므로 지정된 칼럼이 2개임을 알 수 있었다.

그래서 1' UNION SELECT 2,4#를 입력했더니 제대로 나오는 것을 볼 수 있다.

쿼리문이 SELECT FROM 뭔지모르지만어떤테이블명 WHERE 어떤칼럼명='1' UNION SELECT 2,4가 될 테니, 어떤 칼럼의 값이 1인 투플이 admin, admin 이고 2,4는 그냥 내가 한거니까 저렇게 그대로 나온다.

이제 ' UNION SELECT schema_name, 2 from information_schema.schemata#를 입력한다.

information_schema.schemata는 MySQL에서 테이블구조인 스키마를 저장하는 테이블이다. 이 테이블엔 DB에 저장된 스키마들이 저장되어 있다. 지금 서버가 MySQL을 사용하는지, SQL Server을 사용하는지는 모르겠지만 일단 안다는 가정하에 넣었다. SQL 언어의 종류를 알면, 저렇게 테이블에 대한 정보를 저장하는 시스템 DB에 맞게 쿼리문을 작성하면 된다.

 

Microsoft SQL Server는 informaion_schema.schemata와 비슷한 테이블로 sysobjects라는 것이 있다. 데이터베이스에 있는 모든 객체를 제공하는 테이블이다. 따라서 MS SQL Server 언어라면 ' UNION SELECT name FROM sysobjects#가 될 것이다. (컬럼명도 schema_name이 아닌 name이다. 이런 정보는 SQL 언어 시스템 DB 형태에 따라 다르다.)

 

결과를 보면 있는 스키마들이 많은데, 우리가 봐야할 정보들은 dvwa에 있을 것 같다.

그 후에는 ' UNION SELECT table_name, 2 from information_schema.tables where table_schema=

'dvwa'# 라고 입력한다. 이번에는 information_schema.tables라는 테이블에서 정보를 가져오는데, 지금 스키마의 이름은 dvwa라고 알고 있다. 저 information_schema.tables는 테이블의 이름을 저장하고 있는 테이블이다. information_schema.tables에서 테이블 명을 가져오는데, 스키마가 dvwa인 테이블 명을 가져오는 것이다.

결과를 보면 지금 dvwa 스키마에는 guestbook과 users라는 테이블이 존재한다. 우리는 users를 공략해본다.

이번에는 information_schema.columns라는 테이블에서 컬럼 명을 가져온다. 이제 스키마, 테이블 이름도 알았으니 그 테이블에서 참조할 애트리뷰트만 정하면 된다. 이런 정보들은 information_schema.columns에 저장되어 있다. ' UNION SELECT column_name, 2 from information_schema.columns WHERE table_schema='dvwa' AND table_name='users'# 라고 입력하면 알 수 있을 것이다. 

결과를 보면 많은 컬럼들이 나오는데, 우리는 user와 password를 선택했다.

' UNION SELECT user, password FROM users# 가 페이로드가 된다. 성공:D

이제 블라인드 SQL 인젝션 공격을 해보자. 이는 쿼리문의 결과를 직접적으로 알 수 없을 때 사용하는 기법이다. 쿼리문이 참과 거짓일 때의 차이(에러문구)를 분석해 정보를 얻어내는 기법이다.

 

중요한 점은 아까와 다르게 OR 연산자가 아닌 AND 연산자를 사용한다는 점이다. 그리고 정보가 존재하는 값을 하나 이상 알아야 한다. 만약 1을 입력하면 참이 될 때, 사용할 수 있는 방법이다. 만약 참이라는 결과를 하나도 모르면 값을 비교할 수 없게 된다. 

왜냐면 우리가 비교할 것은 SELECT FROM users WHERE id='1' and 'a'='a' 와 SELECT FROM users WHERE id='1' and 'a'='b' 의 결과 차이이기 때문인데, where id='1' 자체부터 FALSE라면 앞 뒤 쿼리를 비교할 수가 없음.

하나 이상의 정보를 안다고 가정하고(SELECT FROM users WHERE id='1' 가 TRUE), 만약 쿼리문을 이용하지 않으면 두 결과가 같을 것이다. 앞은 TRUE이고 뒤는 FALSE가 나오면, SQL인젝션 의심이 가능하다는 말이다. 쿼리문을 이용하지 않으면 두 결과 모두 FALSE가 나올 것이다.

 

하지만 TRUE와 FALSE 관계없이 출력 메시지가 같다면 웹 요청이 응답되는 시간의 차이를 이용한다. sleep, wait for 등의 함수를 이용하는 것이다.

 

실습을 해보면, 아까처럼 '을 입력하는 것만으로는 SQL 인젝션이 가능한지 알 수 없다. user id에 '가 없어서 에러가 나는 것인지 쿼리문 문법이 맞지 않아서 에러가 나는 것인지 구분할 수 없기 때문이다.

1을 넣으면 exist(TRUE)라고 뜬다.

1' and 'a'='a 을 입력해보았다. TRUE.

1' and 'a'='b 을 입력해보았다. FALSE.

따라서 쿼리문이 SQL문으로 이루어짐을 확인할 수 있다.

 

근데, 만약 FALSE / TRUE 관계없이 출력 메시지가 같은 경우에는?.. 응답에 걸리는 시간의 차이를 이용한다고 했다.

진짜인지 확인해보기 위해 이번에는 FALSE인 정보를 하나 알아냈다.

1' and sleep(5)# 을 입력해주었다. 개발자도구에서 응답요청에 걸리는 시간을 알아보니 5026밀리초가 걸린다고 한다.

이번에는 100' and sleep(5)# 을 입력해주었다. 이번에는 30밀리초밖에 안걸렸다.

즉, where id='100' 자체가 FALSE이기 때문에 Sleep자체가 되지 않은 것이므로, id가 1인 투플은 있지만, 100인 투플은 없다고 결론지을 수 있다.

다음으로는 sqlmap을 실습해보았다. sqlmap은 자동화 공격을하는 파이썬 기반 CLI 프로그램이다. CLI니까 터미널로 명령어를 실행하고, 우리의 데이터베이스의 정보를 획득할 수 있다. 

 

많은 옵션들이 있는데 -u는 필수옵션으로 공격을 시도하는 URL를 지정한다. 로그인이 필요할 경우 --cookie로 세션 쿠키값을 지정하고, 이번에 쓴 옵션들은 -D, --current-db, -T, --tables, --columns이다. 각각 데이터베이스를 지정하고, 데이터베이스의 이름을 알아내고, 테이블 이름을 지정하고, 테이블명을 알아내고, 칼럼명을 알아내는 옵션들이다.

 

그 밖의 옵션 정보는 -h로 알 수 있다. 

다시 SQL Injection으로 돌아가서 1을 입력한 URL을 추출한다.

그리고 개발자도구 콘솔에서 document.cookie를 입력해서 쿠키를 추출한다.(맨날 까먹는다.)

그리고 이제 sqlmap -u 'URL주소' --cookie="쿠키값" 을 입력하면, 여러 정보들이 나온다. 캡처 파일이 날라갔는데, 뭐 그냥 몇개의 요청을 보냈고, id라는 parameter가 취약한 점, 페이로드 쓸거냐 물어보는 등의 정보들을 볼 수 있다.

나는 현재 DB의 정보를 알아보고 싶어서 --curent-db를 추가해보았다.

그랬더니 dvwa라는 데이터베이스 명(스키마)을 알 수 있었다. 

이걸 이용해서 테이블명과 컬럼명도 알고 싶었다. 뒤에 -D dvwa --tables --columns 를 추가한다.

정보가 나옴.

알아낸 정보로 값들의 정보를 알고 싶어서 공격을 해보았다. 뒤에 -D dvwa -T users --dump 를 추가한다.

그러면 뭐 다른 툴을 쓸건지, 딕셔너리 쓸건지 물어봐서 딕셔너리 쓰고, 디폴트 파일을 이용하고, common password suffixes도 이용한다고 했다. 결과가 꽤 잘 나온다. 아까는 password 컬럼에 저장된 값들만 보여줬는데, 해쉬값인 걸 알고 원래 값도 알려준다....

 

이제 security 단계를 조정했을 때 어떻게 되는지 알아보겠다. medium으로 바꾸면,

소스 코드가 이렇게 바뀐다. 일단 선택형으로 바뀌고, where id='$id';";가 아닌, where id=$id;";로 바뀌는 것이다. 원래 burp suite을 통해서 인터셉트 해서 forward해봐야 하는데 귀찮다.... 저 소스코드의 뜻은 입력값을 더이상 문자열이 아닌 숫자로 받겠다는 뜻이다. 따라서 버프스위트로 포워드 할 때에도 1' or '1'='1은 먹히지 않는다는 것이다. 1 or 1=1은 먹히고 1 UNION SELECT user.password FROM users 도 가능하다. (나중에 해서 올리겠음)

이번에는 high일 때의 소스코드 모습이다. 뭐 이상한 창도 뜨게 하고, 뒤에 LIMIT 1;까지 붙었다. medium과 달리 숫자로만 받지는 않는다. 그저 무조건 하나만 출력하도록 제한하는 것이다.

우리가 넣는 값은 $id에 들어갈 것인데, LIMIT 1을 안 먹히게 하면 그만이다. 그러므로 주석처리 해버리면 된다. 사실이게 더 취약해보인다. 그냥 1' or 'a'='a'# 해버리면 된다. 

이번에는 impossible level이다. 일단 숫자인지 아닌지도 검사하고, 동적쿼리가 아닌 파라미터 쿼리로 처리해버린다.

여태까지는 mysql 쿼리문의 일부에 우리가 넣는 값이 들어가는 형태(동적 쿼리)였는데, 이제부터는 prepare 뒤가 모두 문자열로 인식되므로 컴퓨터가 모든 것을 문자로 처리하여 파라미터 등을 조작하기 어려워졌다. (파라미터 쿼리)

아직은 공격방법을 모르겠다.

커멘드 인젝션 공격은 SQL 인젝션 공겨고가 같이 인젝션 리스크와 관련된 주요 공격 기법이다.

웹 요청 메시지에 시스템 명령어를 삽입하여 호스트 내부의 명령어를 실행하는 것인데, 웹 서버에서 해당 명령어를 실행하는 것이다.

리눅스는 ;를 이용하여 여러가지 명령어를 동시에 실행하므로 ;을 입력하면 앞의 명령어의 결과는 상관없이 뒤의 커맨드 명령어를 실행시킬 수 있다. 

 

실습을 해보면, 127.0.0.1을 넣었더니 결과가 나온다.

아까 리눅스 명령어가 ;로 구분된다고 했으니 뒤에 ;ls 를 붙여보았다.

이번엔 그냥 ;ls 를 해보았는데, 잘 나온다.

;id 를 쳐봤더니 또 잘 나온다. daemon으로 되어있고, setUID는 없나보다.

이번에는 ;cat /etc/passwd 를 해봤더니 잘 나온다. 많이도 있다...

setcurity level을 medium으로 바꾸고 소스코드를 보았다. ;를 없어지게 처리하고, &&도 없어지게 처리한다. &&는 윈도우에서 같은 역할을 하는 문자열이다.

그래서 ; 대신 파이프를 이용해서 |id를 했더니 결과가 나왔다.

그리고 백그라운드 실행인 &도 해보았다.

이번엔 security level을 high로 바꾼 소스코드는 많은 문자열을 없게 처리한다. 근데 파이프뒤에 공백이 있어야만 무시한다. 그러면 아까처럼 |id를 입력하면 나올 것 같았다.

짜잔

impossible으로 바꿔보았더니, 이렇게 a.b.c.d의 형태(아이피 형태)만을 받는 것을 알 수 있었다. 공격방법은 모르겠다..

 

이제 끝-

'EVI$I0N > 2019-1' 카테고리의 다른 글

[와이어샤크] #02  (0) 2019.04.06
[디지털포렌식] #02  (0) 2019.04.06
[딥러닝] #01  (0) 2019.03.25
[와이어샤크] #01  (0) 2019.03.25
[디지털포렌식] #01  (0) 2019.03.23