Injection Flaws

옥영진·2021년 3월 7일
0

WebGoat

목록 보기
3/9
post-custom-banner

SQL Injection

SQL 인젝션 공격은 클라이언트에서 서버에서 수행되는 SQL 쿼리문에 악의적인 데이터를 삽입하여 DB에 있는 데이터를 임의로 수정 및 추출하는 공격을 말한다.

1)


Account Name 입력칸에 문자열 입력 후 Get Account Info 버튼을 클릭하면 입력한 문자열이 SQL 쿼리문 중 userName 의 값으로 입력되어 쿼리문 실행결과가 출력된다. 이를 이용해 users 테이블에 있는 모든 데이터를 조회하면 되는 문제이다. select 문에서는 where 조건이 참일 경우에만 데이터를 조회하는데, 이를 항상 참인 조건을 추가하면 모든 데이터를 조회할 수 있다.


Smith' or '1'='1 을 입력하면 모든 데이터가 출력된다. 완성된 쿼리문은 select * from users where LAST_NAME = 'Smith' or '1='1' 의 형태가 된다. 쿼리문을 보면 userName 양 끝에 싱글 쿼터가 있는데 이는 문자열임을 나타내는 것이므로, Smith 뒤에 싱글 쿼터를 붙여 하나의 조건을 완성시키고, 뒤에 or 과 항상 참인 조건을 추가하면 userName 이 어떤 값이든 항상 참이기 때문에 모든 데이터를 조회하게 된다.

2)


이전 문제와 마찬가지로 입력칸에 적절한 값을 입력하여 쿼리문을 완성시켜 모든 데이터를 출력하도록 해야한다. 여기서는 값을 입력받아야하는 파라미터인 userID 가 이전 문제와 다르게 숫자형 값을 받아야하므로 싱글 쿼터로 둘러 싸여 있지 않다.


1234 or 1=1 을 입력하면 userID 가 1234 이거나 1=1 을 만족하는 데이터를 출력한다. 1=1 이라는 조건은 결국 항상 참이기 때문에 모든 데이터를 출력하게 되는 것이다.

SQL Injection (advanced)

1)


user_system_data 라는 테이블이 있는데 이를 Union 이나 Join 을 사용하여 Dave의 패스워드를 알아내야 하는 문제이다.


일단 Smith 라는 이름을 검색해보면 총 7개의 칼럼 데이터를 조회하는 것을 알 수 있다. union을 사용하여 user_system_data 테이블의 데이터도 조회할 것인데, 입력값이 문자열 데이터로 취급하므로 쿼리문에서 입력값이 싱글 쿼터로 둘러 싸여 있다고 예상할 수 있다. 따라서 Smith' union select userid, user_name, password, cookie, null, null, null from user_system_data -- 이렇게 입력한다면 user_system_data 의 데이터도 함께 조회할 수 있을 것이다.


union 으로 select 문을 연결할 때 4개의 칼럼명 뒤에 3개의 null 또한 입력했는데 이는 앞에서 select 문이 총 7개의 칼럼 데이터를 조회하는 것을 알 수 있기 때문에 union 을 사용할 때는 칼럼 수를 맞춰야 조회할 수 있다. 따라서 4개의 칼럼명 뒤에 null 을 입력하여 칼럼 수를 맞추면 user_system_data 테이블의 데이터를 조회할 수 있고, 그 중에서 Dave 의 패스워드를 알 수 있다. 그리고 끝에 -- 를 붙인 이유는 원래 쿼리문에서 입력값을 감싸는 싱글 쿼터를 주석처리하기 위함이다.


참고로 union 을 사용하지 않고 '; select * from user_system_data -- 를 입력하면 세미콜론으로 원래 쿼리문을 종료하고 이어서 새로운 select 문을 실행하여 user_system_data 테이블의 데이터를 조회할 수 있다.

2)

Blind SQL Injection

일반적인 SQL Injection의 경우 입력한 쿼리가 어떻게 동작하는지 데이터베이스로부터 에러 메세지를 통해 확인이 가능하다. 하지만 Blind SQL Injection은 오류 메세지 대신에 쿼리의 참과 거짓에 따라 보여지는 결과를 통해 데이터베이스 구조를 파악하며 데이터를 유추해내는 공격을 말한다.


tom 이라는 사용자로 로그인을 하는 것이 목적이다. login 폼에서는 이전에 했던 인젝션 공격으로는 로그인이 되지 않아 계정 등록 폼에서 인젝션을 하기로 했다.


tom' and 1=1 -- 을 입력하여 사용자 등록을 시도하면 이미 존재한다는 메세지가 나온다.


이번에는 tom' and 1=2 -- 를 입력하면 생성되었다는 메세지가 표시되는데, and 조건이 쿼리문으로 적용되지 않고 전체가 문자열 그대로 취급되었을 수도 있지만, 똑같은 값으로 한번 더 등록하려고 하면 이미 등록되었다는 메세지 대신에 또 생성되었다는 메세지가 표시된다. 이를 통해 Username에 입력된 값이 조건식에 포함되어 참일 경우 이미 존재한다는 메세지를 리턴하고, 거짓일 경우 계정이 생성되었다는 메세지를 리턴한다는 것을 추측할 수 있다.


개발자 도구를 통해 Username 입력폼의 이름이 username, username_reg 로 되어 있는데 이와 비슷하게 데이터베이스 칼럼명으로 지어졌을 가능성이 있다. 따라서 칼럼 입력 값 길이를 구하는 함수인 length()를 사용하여 True, False 결과를 확인해보자.


username, id, username_reg 등으로 유추해보았지만 모두 500 에러 패킷을 리턴했고, userid 라는 칼럼명을 사용했을 때 이미 존재한다는 메세지를 출력했다. 아까도 말했지만 이미 존재한다는 메세지는 쿼리가 참일 때 나왔으므로 이를 통해 입력한 쿼리문이 문자열로 처리되지 않고 그대로 실행됨을 확신할 수 있게 되었다.


비밀번호 값도 칼럼명이 password 라고 되어있는 칼럼에 저장됨을 확인했고, 길이 비교를 통해 tom 계정의 비밀번호 길이를 알아보기로 했다.


패스워드 길이가 23과 같은지 비교했을 때 참인 결과 메세지를 출력했으므로 길이가 23임을 알 수 있다. 이제부터 23자리 문자를 하나씩 비교하면서 찾아낼 것이다. 사실 데이터베이스에 이런 패스워드와 같은 정보는 암호화하여 저장하는 것이 필수다. 하지만 WebGoat의 경우 공부 목적으로 개발된 웹 애플리케이션이기 때문에 패스워드가 평문으로 저장되어 있기 때문에 브루트 포스 공격으로 알아낼 수 있는 것이다.


substr() 함수를 통해 비밀번호 한 글자씩 알아내기로 했고, 하나씩 수동으로 하기엔 경우의 수가 많기 때문에 Burp Suite의 기능을 사용하기로 했다.


기본으로 지정되어 있던 것들은 모두 드래그 하여 우측 Clear 버튼을 통해 초기화 한 후, 1~23번째 자리까지 찾을 것이므로 substr 함수 내 두 번째 인자값과 a~z 중 하나를 찾을 것이므로 비교하는 문자부분을 payload가 삽입될 부분으로 지정한다.


첫 번째 payload는 순차적 숫자 값으로 1~23으로 입력한다.


두 번째 payload는 a~z 까지 브루트 포스 타입으로 설정하고, 앞에서도 언급했지만 이건 공부 목적의 웹 애플리케이션이기 때문에 패스워드도 문자로만 이루어져있음을 알아야 한다. 그렇기 때문에 지금은 경우의 수가 598가지 밖에 나오지 않지만, 패스워드가 대소문자, 특수문자, 숫자 등으로 복잡하게 이루어지면 브루트 포스로 공격하려면 엄청나게 큰 경우의 수가 생기기 때문에 그만큼 시간이 오래 걸린다.


community 버전이라 598가지의 경우의 수라도 시간이 꽤 걸렸다. 결과 화면을 보면 길이가 유독 다른 패킷들이 몇 가지 보일 것이다. 위 캡쳐 화면에서 보이듯이 substr(password, 7, 1)='a' 결과가 참임을 알 수 있다. 따라서 패스워드의 7번째 자리의 문자는 a임을 알 수 있다. 이런식으로 23자리의 패스워드를 모두 알아내면 tom 계정으로 로그인할 수 있게 된다.

SQL Injection (mitigation)

1)


이번 문제는 webgoat-prd 서버의 IP 주소를 알아내는 것이 목표이다. IP 주소가 xxx.130.219.202 로 주어지는데 첫 번째 옥텟 값만 알아내면 된다.


문제를 풀기 전에 이 파트에서 나온 설명 중 order by 절을 통해 Injection 공격하는 방법을 알려준다. 이는 case when 뒤에 오는 조건의 참과 거짓에 따라 어떤 칼럼으로 정렬되는지 확인하는 방식이다.


문제 화면에서 칼럼 옆 화살표를 누르면 해당 칼럼으로 정렬을 하게 되는데, 그 때 보내는 패킷을 확인해보면 column 파라미터 값으로 칼럼명이 지정되는 것을 알 수 있다. 즉, 서버에서 order by 절에 해당하는 값이 파라미터로 지정되므로 해당 값을 조작하여 인젝션 공격이 가능할 수도 있다는 예상을 할 수 있다. 이 패킷을 Reapeater로 보내서 파라미터 값을 조작해보기로 했다.



파라미터 값이 order by 다음 값으로 지정되는 것으로 예상되기 때문에 case when 조건절을 활용하여 참일 때는 id, 거짓일 때는 description으로 정렬 되도록 파라미터로 값을 지정했더니 응답값으로 오류 없이 쿼리문 실행 결과값이 나오는 것을 알 수 있다. 그렇다면, 문제를 해결하려면 hostname이 webgoat-prd인 데이터의 ip를 이전에 패스워드를 구했을 때처럼 substr 함수를 사용하면 알아낼 수 있을 것이다.

그 전에 hostname이 webgoat-prd인 데이터의 ip 값을 구해야 하므로 서브쿼리를 사용해야 한다. 문제에서는 테이블명에 대한 정보가 없기 때문에 추측을 해야하는데, 요청 패킷을 보면 url이 servers라고 되어 있어 일단 테이블명을 servers로 예상해 보았다.


일단 문제에 나와있는 hostname이 webgoat-dev에 대하여 ip의 두번째 값이 9가 참이면 id 칼럼으로 정렬하도록 했더니, 정확하게 id 값으로 정렬이 되었다. (9가 아닌 다른 값을 넣으면 거짓이 되어 description 칼럼 값 기준으로 정렬이 되었다.) 그렇다는 건, 테이블명이 servers가 맞다는 것이 되고, 이제 hostname이 webgoat-prd의 ip 값의 첫번째 옥텟값을 하나 하나씩 알아내보자.


ip 첫번째 값은 1임을 알 수 있다.


두번째 값은 0임을 알 수 있다.


마지막 세번째 값은 4임을 알 수 있다.

이렇게 해서 정답은 104.130.219.202 임을 알게 되었다.

XXE

XXE(XML External Entity) 인젝션 공격은 XML 구문을 파싱하는 애플리케이션을 대상으로 하는 공격이다.


XXE 공격을 통해 중요 정보가 유출도리 수 있고, CSRF 공격도 가능하다는 등의 내용이 있고, 아래에 XXE 공격의 종류가 나와있다.

  • DTD(Document Type Definition)에 외부 엔티티가 포함될 때 하는 공격
  • 응답으로 에러 등의 반응이 없을 때 하는 블라인드 인젝션 공격
  • 에러 메시지를 통해 정보를 제공받아 이를 이용하는 공격

OWASP 사이트에서 XXE 관련 문서를 찾아보면 관련 공격의 예시가 있다.

1)


문제를 살펴보면 XXE 인젝션 공격을 통해 파일 시스템의 루트 디렉토리 내용을 출력하라고 되어 있다. 일단 입력폼에 문자열 입력 후 submit 버튼을 클릭하면 다음과 같은 요청 패킷을 확인할 수 있다.


요청 바디가 xml 문서로 되어 있고, 헤더 중에 Cotnent-Type의 값에도 application/xml 이라고 되어 있다. 일단, 이론에 나와 있던 DTD 정의 부분을 약간 수정한 후 요청 바디에 삽입하여 보내보았다.


이렇게 수정하여 보내면 입력폼에 입력했던 값이 아닌 Jo Smith라는 문자열이 댓글로 작성된다. 그렇다면 OWSAP 에서 예시로 나와있듯이, 외부 엔티티를 정의하여 루트 디렉토리의 내용을 출력해보자.


문서 외부에 존재하는 외부 엔티티는 SYSTEM 키워드를 통해 정의할 수 있는데, 경로를 /루트 디렉토리로 설정하면 아래처럼 루트 디렉토리의 내용이 출력된다.

2)


REST framwork를 사용하는 서버에서는 JSON 형태의 데이터를 처리할 때 XXE 공격에 취약할 수 있다는 내용이다.


댓글을 달았을 때 패킷을 확인해 보면 json 형태의 바디 데이터로 보내는 것을 알 수 있다. 이전과 같이 xml 형식으로 변경해서 패킷을 전송해보았다.


바디 데이터를 xml 형태로 보내보았지만 이전처럼 디렉토리 내용이 댓글에 등록되지 않았다. 헤더를 살펴보니 클라이언트에서 보내는 데이터 형식을 알려주는 Content-Type 값이 application/json 이라는 것을 알 수 있었다. 그래서 json 대신 xml로 바꿔서 한 번 보내보았다.


Content-Type 값을 xml로 바꿨더니 json 데이터를 처리하는 서버에서 xml 형태로 처리하여 루트 디렉토리 내용이 댓글에 등록되었다.

3)


WebWolf(공격자 서버)를 사용하여 WebGoat 서버 내 /home/USER/.webgoat-8.0.0.M24/XXE/secret.txt 파일 내용을 알아내어 댓글로 작성하면 되는 문제인 것 같다. 문제에서는 WebWolf에 인젝션 DTD 파일을 업로드 한 후, WebGoat에서 이 파일에 접근하게 하라고 되어 있다. 그렇다면 WebGoat에서 댓글 작성 시 XXE 공격을 해서 WebWolf에 업로드 된 파일에 접근하게 한 뒤 XML 파서가 이를 해석했을 때 secret.txt 파일 내용을 로그에 남기게 하면 될 것 같다.


attack.dtd 라는 DTD 파일을 생성한 후,


이를 WebWolf 서버에 업로드 하고 파일에 접근할 수 있는 링크를 획득했다. 이 링크를 WebGoat에서 XXE 공격 시 외부 엔티티 경로로 설정하면 읽게 될 것이다.


외부 엔티티 정의할 때 % 기호를 사용했는데 이를 파라미터 엔티티라고 부른다. 이 파라미터 엔티티는 DTD 내부에서 참조할 수 있게 된다. 즉, attack.dtd 파일 내에 정의된 attack 엔티티를 참조할 수 있게 되고, 엔티티 참조를 통해 파일 내용을 %attack; 을 통해 댓글로 작성하게 된다.


댓글을 보면 secret.txt 파일 내용이 댓글에 등록된 것을 알 수 있다. 작성된 이 내용을 댓글로 다시 작성하면 문제가 해결된 것으로 된다.

profile
안녕하세요 함께 공부합시다
post-custom-banner

0개의 댓글