ⓐ SQL Injection : SQL 문 사용 시 공격자의 입력 값이 정상적인 요청에 영향을 주는 취약점
ⓑ Command Injection : OS Command 사용 시 사용자의 입력 데이터에 의해 실행되는 Command를 변조 할 수 있는 취약점
ⓒ Server Side Template Injection(SSTI) : 템플릿 변환 도중 사용자의 입력 데이터가 템플릿으로 사용돼 발생하는 취약점
ⓓ Path Traversal : URL / File Path를 사용 시 사용자의 입력 데이터에 의해 임의의 경로에 접근하는 취약점
ⓔ Server Side Request Forgery(SSRF) : 공격자가 서버에서 변조된 요청을 보낼 수 있는 취약점
ⓐ-1 SQL Injection
웹 어플리케이션에서 로그인/검색과 같이 사용자의 입력 데이터를 기반으로 DBMS에 저장된 정보를 조회하는 기능 등을 구현하기 위해 SQL 쿼리에 사용자의 입력 데이터를 추가하여 DBMS에 요청하는데, 그 과정에서 사용자의 입력이 SQL 쿼리에 삽입되어 SQL 구문으로 해석되거나 문법적으로 조작하게 되면 개발자가 의도하지 않은 임의의 쿼리가 실행될 수 있다.
즉, SQL 쿼리에 사용자의 입력 값이 삽입돼 사용자가 원하는 쿼리를 실행할 수 있는 취약점이다.
대표적인 예시로, 로그인 시
SELECT * FROM users WHERE uid = '{uid}' and upw='{upw}'; 라는 쿼리가 실행될 때
'를 기준으로 문자열을 구분하고 있다는 점을 집중해서 보자.
만약 사용자가 입력에 ' 문자를 포함해 해당 문자열을 탈출하고 뒷 부분에 새로운 쿼리를 작성하여 전달하면 DBMS는 사용자가 직접 작성한 쿼리를 실행시키는 것이다.
'{uid}' 에 admin을 , '{upw}' 에 1'or'1 을 작성한다면, or 연산을 이용해 참의 조건이 만족되므로 admin의 데이터에 접근이 가능하게 된다.
+ (or연산은A or B와 같은 구조일 경우 A와 B 중 하나라도 참(True)인 경우 결과는 참이된다.)
ⓐ-2 SQL Injection 방어
SQL Injection을 막기 위해 권장하는 방법
:: ORM(Object Relational Mapper의 약자로써 SQL의 쿼리 작성을 돕기위한 라이브러리)과 같이 검증된 SQL 라이브러리 사용
이를 통해 개발자가 직접 쿼리를 작성하는 Raw 쿼리를 사용하지 않아도 기능 구현이 가능하여, SQL Injection으로부터 상대적으로 안전하다.
ORM은 생산성을 위해서도 사용하지만 사용자의 입력 값을 라이브러리 단에서 스스로 escape하고 쿼리에 매핑시키기 때문에 안전하게 SQL 쿼리를 실행한다.
ⓑ-1 Command Injection
:: OS Command는 linux (ls, pwd, ping, zip..), windows (dir, pwd, ping..) 등의 OS에서 사용하는 명령어
:: 이러한 명령어를 실행하기 위해 PHP, NodeJS, Python과 같은 함수들이 존재한다.
OS Command는 내부적으로 셸(Shell)을 이용해 실행하는데, 셸은 사용자 편의성을 위해 한 줄에 여러 명령어를 실행하는 특수 문자가 여럿 존재한다.
따라서 이를 사용할 때 사용자의 입력 값을 검증하지 않고 함수의 인자로 전달한다면?
여러 특수 문자를 이용해 사용자가 원하는 명령어를 함께 실행할 수 있다.
` `
명령어 치환
`` 안에 들어있는 명령어를 실행한 결과로 치환된다.
$( )
명령어 치환
$( ) 안에 들어있는 명령어를 실행한 결과로 치환. 중복 가능
&&
명령어 연속 실행
한 줄에 여러 명령어를 사용하고 싶을 때. 앞 명령어에서 에러가 발생하지 않아야 뒷 명령어 실행 가능
||
명령어 연속 실행
한 줄에 여러 명령어를 사용하고 싶을 때. 앞 명령어에서 에러가 발생해야 뒷 명령어 실행 가능
;
명령어 구분자
한 줄에 여러 명령어를 사용하고 싶을 때. 단순 구분자. 앞 명령어의 에러 유무 상관없이 뒷 명령어도 실행
@app.route('/admin/notice_flag')
def admin_notice_flag():
global memo_text
if request.remote_addr != '127.0.0.1':
return 'Access Denied'
if request.args.get('userid', '') != 'admin':
return 'Your not admin'
memo_text += f'[Notice] flag is {FLAG}\n'
return 'Ok'
해당 페이지 코드를 보니 고려해야 할 if 문이 두 개나 있다.
① request.remote_addr != '127.0.0.1' : 접속한(클라이언트의) IP 주소가 127.0.0.1이 아니라면~
② request.args.get('userid',' ') != 'admin' : userid에 해당하는 값이 admin이 아니라면~
접속한 IP 주소가 127.0.0.1이 아니었기 때문에 Access Denied가 뜬 것이다.
그렇다면 접속 IP주소를 127.0.0.1로, userid 값을 admin으로 설정해준다면
global 변수인 memo_text에 flag 값이 들어가 memo 페이지에 써져 있을 것이라고 생각했다.
그림 5 flag 페이지
@app.route('/flag', methods=['GET', 'POST'])
def flag():
if request.method == 'GET':
return render_template('flag.html')
elif request.method == 'POST':
csrf = request.form.get('csrf', '')
if not read_url(csrf):
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'
그림 6 app.py 일부. read_url( )
flag 페이지에선 'csrf'의 값을 알아낸 후 read_url() 함수에 넣어 실행시키고 있다.
마지막 부분인
driver.get(f'http://127.0.0.1:8000/csrf?csrf={urllib.parse.quote(url)}') 에 의해 해당 페이지가 불러와지므로
csrf 페이지 내에서 공격을 하기로 했다.
csrf 페이지에서 공격할 때 frame, script, on 태그는 사용하지 못하므로
img 태그를 사용했다.
<img src="http://127.0.0.1:8000/admin/notice_flag?userid=admin"> 입력