출처: dreamhack-webhacking 03. server-side basic https://dreamhack.io/lecture/courses/15
서버에서는 사용자가 요청 한 데이터를 해석하고 처리한 후 사용자에게 응답합니다. 그 과정에서 웹 어플리케이션이나 데이터베이스와 같은 서버의 자원을 사용해 처리하기도 합니다. 이 과정에서 사용자의 요청 데이터에 의해 발생하는 취약점을 서버 사이드 취약점이라고 합니다.
"Introduction of Webhacking" 강의에서는 HTTP Request를 네트워크 프로그램을 통해 전송하는것을 다뤘습니다.
nc나 telnet과 같은 네트워크 프로그램을 통해 HTTP Request를 보낼 때는 메소드나 요청 헤더처럼 브라우저에서 조작하지 못하는 데이터도 조작하여 전송할 수 있기 때문에 서버에서는 사용자로부터 받는 모든 입력을 신뢰하지 않도록 해야합니다.
네트워크 프로그램으로 데이터 조작하여 전송가능
http request(http 요청) : header body
echo : 텍스트 출력, 간단한 파일 만들기 가능 (https://m.blog.naver.com/PostView.naver?blogId=audiendo&logNo=220775822302&proxyReferer=https:%2F%2Fwww.google.com%2F)
echo -ne : 줄바꿈제거, 특수문자 처리(\n같은)
* echo -n : 한줄로 만든다.
(https://knight76.tistory.com/entry/shell-script-%ED%95%9C-%EC%A4%84-%ED%91%9C%ED%98%84-echo-n)
echo -e : \n이나 \r 특수문자로 인식(https://www.lesstif.com/lpt/echo-19857474.html)
nc(netcat) : cat처럼 파일 읽고 쓰기 기능, 네트워크의 cat
네트워크에서 데이터를 읽고 쓰기 기능
usage : nc [options] [target host] [ports] (https://m.blog.naver.com/PostView.naver?blogId=carmine1025&logNo=220554474008&proxyReferer=https:%2F%2Fwww.google.com%2F)
foobar은 아무 의미 없다 (https://endorphin0710.tistory.com/91)
연두색: 웹 서버에 전송할 데이터를 직접 입력하여 전송하는 명령어
웹 서비스를 제공하는 서버를 대상으로 공격을 시도하는 서버 사이드 취약점의 주 목적은 서버 내에 존재하는 사용자들의 정보를 탈취하거나, 서버의 권한을 장악하는 등이 있습니다.
<취약점>
- Injection (인젝션)
- 서버의 처리 과정 중 사용자가 입력한 데이터가 처리 과정의 구조나 문법적으로 사용되어 발생하는 취약점입니다.
- File vulnerability
- 서버의 파일 시스템에 사용자가 원하는 행위를 할 수 있을 때 발생하는 취약점입니다.
- Business Logic Vulnerability (비즈니스 로직 취약점)
- 인젝션, 파일 관련 취약점들과는 다르게 정상적인 흐름을 악용하는 것을 의미합니다.
- Language specific Vulnerability (PHP, Python, NodeJS)
- 웹 어플리케이션에서 사용하는 언어의 특성으로 인해 발생하는 취약점을 다룹니다.
- Misconfiguration
- 잘못된 설정으로 인해 발생하는 취약점을 다룹니다.
<Injection>
Injection(인젝션)은 주입이라는 의미를 가지고 있습니다. 인젝션 공격은 사용자의 입력 값이 어플리케이션의 처리 과정에서 구조나 문법적인 데이터로 해석돼 발생하는 취약점을 의미합니다. 변조된 입력을 주입해 의도하지 않은 행위를 발생시킵니다.
- SQL Injection
- SQL을 사용할 때 공격자의 입력 값이 정상적인 요청에 영향을 주는 취약점입니다.
- Command Injection
- OS Command를 사용 시 사용자의 입력 데이터에 의해 실행되는 Command를 변조할 수 있는 취약점입니다.
- Server Side Template Injection (SSTI)
- 템플릿 변환 도중 사용자의 입력 데이터가 템플릿으로 사용돼 발생하는 취약점입니다.
- Path Traversal
- URL / File Path를 사용 시 사용자의 입력 데이터에 의해 임의의 경로에 접근하는 취약점입니다.
- Server Side Request Forgery (SSRF)
- 공격자가 서버에서 변조된 요청을 보낼 수 있는 취약점입니다.
<<SQL Injection>>
SQL(Structured Query Language)는 관계형 데이터베이스(RDBMS)의 데이터를 정의하고 질의, 수정 등을 하기 위해 고안된 언어입니다. 데이터베이스에 의존하는 상당수의 웹 어플리케이션은 SQL을 사용해 데이터베이스와 상호작용 합니다.
웹 어플리케이션에서 로그인/검색과 같이 사용자의 입력 데이터를 기반으로 DBMS에 저장된 정보를 조회하는 기능을 구현하기 위해 SQL쿼리에 사용자의 입력 데이터를 추가하여 DBMS에 요청합니다.
이 과정에서 사용자의 입력이 SQL 쿼리에 삽입되어 SQL 구문으로 해석되거나 문법적으로 조작하게 되면 개발자가 의도한 정상적인 SQL 쿼리가 아닌 임의의 쿼리가 실행될 수 있습니다.
즉 SQL Injection은 SQL 쿼리에 사용자의 입력 값이 삽입돼 사용자가 원하는 쿼리를 실행할 수 있는 취약점입니다.
SQLInjection이 발생하게 되면 현재 쿼리를 실행하는 DBMS 계정의 권한으로 공격이 가능하며 일반적으로 데이터베이스의 내용을 추출하거나 변조, 삭제하는 등의 행위가 가능합니다.
SQL의 구조
SQL(Structured Query Language)은 말 그대로 구조화된 형태를 가지는 언어입니다. 올바른 구조로 요청해야 데이터베이스가 이해하고 요청한 데이터를 수행하게 됩니다.
SQL은 사용 목적과 행위에 따라 다양한 구조가 존재하며 대표적으로 아래와 같이 구분됩니다.
- DDL (Data definition language)
- 데이터를 정의하기 위한 언어입니다. 데이터를 저장하기 위한 스키마, 데이터베이스의 생성/수정/삭제 등의 행위를 수행합니다.
- DML (Data manipulation language)
- 데이터를 조작하기 위한 언어입니다. 실제 데이터베이스 내에 존재하는 데이터에 대해 조회/저장/수정/삭제 등의 행위를 수행합니다.
- DCL (Data control language)
- 데이터베이스의 접근 권한 등의 설정을 하기 위한 언어입니다. 데이터베이스내에 사용자의 사용 권한을 부여하기 위한 GRANT와 권한을 박탈하는 REVOKE가 대표적입니다.
-DDL
- CREATE
- 새로운 데이터베이스 또는 테이블을 생성합니다.
- create table 테이블이름 (
- 컬럼명 데이터타입 조건,
- 컬럼명 데이터타입 조건,
- );
- 데이터타입
- AUTO_INCREMENT : num 자동으로 순서대로 증가 1,2,3, ...
- VARCHAR(숫자) : 가변 길이 문자열 최대길이 지정 --VARCHAR(100) //문자열 길이 100까지
- 조건
- NOT NULL : 값이 꼭 입력되어야함, null 허용 안함
- PRIMARY KEY : 기본키를 지정할 때 사용 -- idx로 데이터를 식별,구분한다. (https://jhnyang.tistory.com/71)
- 출처: (https://jhnyang.tistory.com/307) 모든 테이블에는 기본 키가 있어야한다.
- ALTER
- 데이터베이스 또는 테이블의 속성을 변경합니다.
ALTER TABLE '테이블명' ADD '컬럼이름' '컬럼타입' (https://infodbbase.tistory.com/51)
createdDate 추가
- DROP
- 데이터베이스 또는 테이블을 삭제합니다.
DROP TABLE '테이블명'
-DML
- INSERT
- 테이블에 새로운 데이터를 추가합니다.
INSERT INTO 테이블 이름 (열1, 열2, ...)
VALUE (값1, 값2 , ….) //idx는 자동 증가이므로 선택 안해도 된다.
또는
INSERT INTO 테이블이름 ( 열1,열2,열3,열4 ….)
SELECT 테이블에 들어갈 값
FROM selct값을 구하기 위한 테이블
WHERE 조건
출처: https://121202.tistory.com/25 [안방철의 방구석]
- UPDATE
- 테이블에 존재하는 데이터를 수정합니다.
- UPDATE [테이블명] SET [열] = '변경할값' WHERE [조건]
출처: https://121202.tistory.com/28 [안방철의 방구석] - SELECT
- 테이블에 존재하는 데이터를 조회합니다. select from where
SELECT [열]
FROM [테이블]
WHERE [조건]
--select * from [테이블] : 전체 열 확인 가능 (where구문은 생략 가능)
출처: https://121202.tistory.com/26 [안방철의 방구석]
- DELETE
- 테이블에 존재하는 데이터를 삭제합니다.
DELETE FROM 테이블WHERE 조건
사용자의 입력 데이터가 SQL 쿼리에 들어가는 대표적인 예시로는 로그인 기능입니다.
사용자가 아이디와 패스워드를 입력해 서버에 전송하면 서버는 해당 데이터가 데이터베이스에 존재하는지 확인하고 로그인 성공 여부를 판단합니다.
사용자는 uid와 upw를 입력한다.
데이터 베이스는 user_table에서 uid=입력값 upw=입력값 조건으로 모든 열을 조회한다.
and로 연결되어있으므로 uid와 upw가 테이블 데이터와 모두 일치해야한다.
사용자가 자신의 아이디와 패스워드를 입력하면 웹 어플리케이션에서는 {uid} , {upw} 부분에 사용자가 입력한 문자열을 삽입하고 DBMS로 전달해 실행합니다.
위 SQL 쿼리는 사용자의 입력과 웹 어플리케이션이 작성한 SQL 쿼리를 해석할 때 ' 를 기준으로 문자열을 구분하고 있습니다. 만약 사용자가 입력에 ' 문자를 포함해 문자열을 탈출하고 뒷 부분에 새로운 쿼리를 작성하여 전달하면 DBMS에서 사용자가 직접 작성한 쿼리를 실행할 수 있습니다.
select * from user_table where uid='' and upw='';
select * from user_table where uid='1' or '1' and upw='1' or '1';
uid가 1은 false이다 or '1' 은 true이므로 false or true --> true
upw='1'은 false, '1'은 true이므로 false or true 는 -->true
true and true --> true이므로 조건은 항상 true , 모든 데이터값 출력
uid=dfdhf
upw=dfjldf'or'1
이렇게 해도 된다.
select * from user_table where uid='dfdf' and upw='dfdf'or '1';
논리연산은 차례대로 진행되므로 ~~false or '1' 은 항상 true 이다. 마지막 값이 중요
앞의 조건은 false이므로
uid=2'or'1
upw=3'or'1 로 해도 된다. or뒤의 값만 참이면 된다.
2'or'1=1 이렇게 해도 된다. 1=1은 true이므로
'or''='
이렇게 하면 '' = '' null=null이므로 참이 된다.
정리: uid = 1'or'1 //1 'or'1하면 띄어쓰기 반영된다. 주의
pwd=1'or'1
하면 항상 True가 되어 데이터 접근 가능
admin만 출력하려면
문제엔 없지만 --주석처리를 이용하여
uid=admin'--
upw=1
이렇게 하면 uid는 admin이고 그 뒤는 다 주석으로 무시되어서(and도 무시됨)
admin 테이블 값이 나온다.
select * from user_table where uid='' and upw='';
select * from user_table where uid='admin'-- or 1' and upw='1';
또는 dreamhack 답처럼
uid=admin'or'
upw=아무거나
or'을 붙이는 이유는 안붙이면
select * from user_table where uid='' and upw='';
select * from user_table where uid='admin'' and upw='156';
이 되므로 156' 이 에러 발생시킨다.
admin''이렇게 하면
select * from user_table where uid='admin''' and upw='156';
논리연산이 이상하므로 or을 붙여준다.
select * from user_table where uid='admin'or'' and upw='156';
true or false --> true
uid가 admin을 살려야하므로 이렇게 하는거고
uid=admin'or'
upw=156'or'1
이렇게 해버리면 다 나온다.
결과: select * from user_table where uid='admin'or'' and upw='156'or'1';
~~~or'1'은 true
근데 'or'2해도 나온다. ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
uid=admin' or 'kk
upw=아무거나
admin만 가능
admin'or'
upw=아무거나
select * from user_table where uid='' and upw='';
select * from user_table where uid='admin' or 'kk' and upw='lolo';
uid=admin은 true이다. 그래서 uid=admin인 테이블 값이
upw='1'은 false ???
uid='admin'이 true이면 true or false여서 다 true가 출력되나보다.
SQL Injection 취약점을 막기 위해서는 사용자의 입력 데이터가 SQL 쿼리로 해석되지 않아야 합니다.
앞서 살펴본 문제점은 사용자의 입력 값에 문자열 구분자(', ")가 삽입되어 본래의 쿼리 형태를 벗어나는 SQL Injection이 발생했습니다.
--과거에는 문자열 구분자 앞에 백슬래시(\) 문자를 붙여 사용자의 입력을 escape해 사용하는 방식을 자주 이용했지만 최근에는 권장하지 않는 방식입니다.
왜냐하면 사용자로부터 입력을 받는 타입은 아이디, 패스워드와 같이 문자열일 수도 있지만 숫자 타입일 수도 있기 때문입니다. 예를 들어 게시판의 게시글을 조회할 때는 게시글의 제목을 쿼리에 사용하는 것보다 게시글의 순서를 나타내는 게시글의 순번으로 조회하는 것이 더 간단하기 때문에 이를 보편적으로 사용합니다.
이 경우에는 문자열 구분자를 모두 escape 했더라도 숫자 뒤에 공백문자(' ', \n, ...)를 넣는 것 만으로도 바로 쿼리에 사용자의 입력을 삽입할 수 있기 때문에 SQL Injection이 발생할 수 있습니다.
다음은 문자열 구분자를 삽입하지 않고 SQL Injection을 통해 where 절의 조건을 항상 참이 되도록하는 예제입니다.
where조건절이 or 1=1(true) ---> true이므로 조건 항상 true
SQL Injection을 막기 위해 권장하는 방법은 ORM과 같이 검증된 SQL 라이브러리를 사용하는 것입니다. 이를 통해 개발자가 직접 쿼리를 작성하는 Raw 쿼리를 사용하지 않아도 기능 구현이 가능하여, SQL Injection으로부터 상대적으로 안전합니다.
여기서 ORM이란 Object Relational Mapper의 약자로써 SQL의 쿼리 작성을 돕기위한 라이브러리 입니다. ORM은 생산성을 위해서도 사용하지만 사용자의 입력 값을 라이브러리 단에서 스스로 escape하고 쿼리에 매핑시키기 때문에 안전하게 SQL 쿼리를 실행합니다.
sqlalchemy는 파이썬에서 사용가능한 ORM
query: 데이터베이스에 정보를 요청
db = SQLAlcemy(...) : import된 SQLAlchemy를 불러와 변수 db를 만들어 메모리에 인식시킨다.
데이터베이스에 정의한 후 테이블을 생성한다. (class)
user테이블에 정보요청, uid와 upw가 같은 모든 것으로, all()은 리스트를 반환한다.
<Command Injection>
웹 어플리케이션에서는 OS Command를 실행하기 위해 PHP(system), Node JS(child_process), Python(os.system)과 같은 함수가 존재합니다.
OS Command란 linux(ls, pwd, ping, zip), windows(dir, pwd, ping) 등의 OS에서 사용하는 명령어입니다.
*OS : 운영체제(리눅스.유닉스...)
일반적으로 웹 어플리케이션에서 OS Command를 사용하는 이유는 이미 기능을 구현한 OS 실행 파일이 존재할 때 코드 상에서 다시 구현하지 않고 이를 실행하면 더 편리하기 때문입니다.
OS Command는 내부적으로 셸(Shell)을 이용해 실행하는데, 셸은 사용자 편의성을 위해 한 줄에 여러 명령어를 실행하는 특수 문자가 여럿 존재합니다. 만약 OS Command를 사용할 때 사용자의 입력 값을 검증하지 않고 함수의 인자로 전달한다면 오른쪽 탭의 특수 문자를 이용해 사용자가 원하는 명령어를 함께 실행할 수 있습니다.
메타 문자설명
`` | 명령어 치환 ``안에 들어있는 명령어를 실행한 결과로 치환됩니다. |
|
$() | 명령어 치환 $()안에 들어있는 명령어를 실행한 결과로 치환됩니다. 이 문자는 위와 다르게 중복 사용이 가능합니다. (echo $(echo $(echo theori))) |
|
&& | 명령어 연속 실행 한 줄에 여러 명령어를 사용하고 싶을 때 사용합니다. 앞 명령어에서 에러가 발생하지 않아야 뒷 명령어를 실행합니다. (Logical And) |
|
|| | 명령어 연속 실행 한 줄에 여러 명령어를 사용하고 싶을 때 사용합니다. 앞 명령어에서 에러가 발생해야 뒷 명령어를 실행합니다. (Logical Or) |
|
; | 명령어 구분자 한 줄에 여러 명령어를 사용하고 싶을 때 사용합니다. ;은 단순히 명령어를 구분하기 위해 사용하며, 앞 명령어의 에러 유무와 관계 없이 뒷 명령어를 실행합니다. |
|
| | 파이프 앞 명령어의 결과가 뒷 명령어의 입력으로 들어갑니다. |
'' 명령어 치환
''안 명령어 실행
$ echo `echo theori`theori //echo실행 두번
$() 명령어 치환
()안 명령어 실행
$ echo $(echo theori)theori //echo 두번
echo $(echo $(echo theori)) //echo 세번
&& 명령어 연속 실행 true&&
앞이 정상실행되어야(에러없이) 뒤가 실행
$ echo hello && echo theorihellotheori //echo 2번
|| 명령어 연속 실행 false||
앞이 에러가 생겨야 뒤가 실행
$ cat / || echo theoricat: /: Is a directorytheori
cat /는 디렉토리이므로 에러생김
; 명령어 구분자
명령어 여러개 쓸 때 사용
$ echo hello ; echo theorihellotheori //echo 2개
| 앞 명령어가 뒷 명령어의 입력으로 들어감
$ echo id | /bin/sh uid=1001(theori) gid=1001(theori) groups=1001(theori)
사용자가 일반적인 IP나 도메인 주소를 전달하면 해당 주소를 인자로 ping 명령어를 실행하는 간단한 함수입니다. 하지만 앞에서 배운 Meta 문자를 입력할 경우 ping 명령어 외에도 다른 OS Command를 실행할 수 있습니다.
코드설명:
웹표현은 route('/')를 사용하고 route장식자는 해당url요청이 들어왔을 때 어떤 함수를 실행시킬 건지 결정한다.
@는 장식자를 나타낸다. (다음행부터 장식자 적용)
여기서 app은 Flask 인스턴스 생성일 것이다.
from flask import Flask
app=Flask(__name__)
(출처:https://m.blog.naver.com/21ahn/221830372908)
>> 정리하면 import된 플라스크의 /ping루트 =/ping page요청에 대한 handler라고도 함
flask로부터 데이터 가져오기
파라미터 key가 정해져있다면 request.args.get({key}) 형태로 받아올 수 있습니다.(import request해야함)
get으로 전달된 데이터= url에서 ?num=123 처럼 전달되는 파라미터(인자) (https://wings2pc.tistory.com/entry/%EC%9B%B9-%EC%95%B1%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%94%8C%EB%9D%BC%EC%8A%A4%ED%81%ACPython-Flask-Request-get-parameterHTTP-method-GET-POST)
ping명령어: 해당 도메인의 IP를 확인할 때 사용
ping [IP 또는 도메인]
ex) ping google.co.kr
결과: IP는 172.217.25.3
(https://extbrain.tistory.com/100)
ping -c : 전송할 요청 패킷의 횟수 정함(count)ping -c 3 {ip} //3번의 요청 패킷ping -c 2 192.168.0.1(https://soyammou.tistory.com/6)
인젝션 하려면 명령어를 집어넣어야하니까 구분자;를 이용하여 명령어 하나 더 삽입
ping -c 3 ;
이렇게 하면 된다.
ping -c 3 ;id
-> id 명령어 실행
ping -c 3 1&& id
-> id 명령어 실행 (1은 정상적으로 실행되므로)
Command Injection 취약점을 막기 위해서는 사용자의 입력 데이터가 Command 인자가 아닌 다른 값으로 해석되는 것을 방지해야 합니다.
가장 좋은 방법은 웹 어플리케이션에서 OS Command를 사용하지 않는 것입니다.
웹 어플리케이션에서 필요한 OS Command가 라이브러리 형태로 구현되어 있으면 해당 라이브러리를 사용하는 것을 권장하며, 없을 경우 직접 프로그램 코드로 포팅해 사용하는 것이 좋습니다.
만약 OS Command에 사용자의 입력 데이터를 사용해야할 경우 필터링을 통해 Command Injection을 방지해야합니다. 방지하는 방법에는 크게 화이트/블랙 리스트 필터링 방식이 있습니다.
- 정규식을 통한 화이트리스트방식 필터링
e.g. ping을 보내는 페이지의 경우 사용자가 입력한 IP가 정상적인 IP 형식인지 정규식으로 검증 후 사용할 수 있습니다.
[^[:alnum:]]|select|delete|update|insert|create|alter|drop"
=> 알파벳과 숫자만을 대상으로 하며 이중 인젝션 가능한 문자인 select, delete, update, insert, create, alter, drop을 제외하는 정규식
(https://sugerent.tistory.com/135)
- OS Command에서 Meta 문자로 사용되는 값을 필터링 하고 따옴표로 감싸기
e.g. ping을 보내는 페이지의 경우 사용자가 입력한 IP를 따옴표로 감싸서 사용할 수 있습니다.
Double Quotes (")를 사용할 경우 dollarsign ($), backquote (`) 가 해석되어 모든 입력을 문자열로 처리하는 Single Quotes (')를 사용해야 합니다. (https://dreamhack.io/forum/qna/441)
'은 모든 입력을 문자열로 처리한다.
- execve args 인자로 사용
shell meta 문자로 해석되지 않도록 입력 값을 넣습니다.
- 쉘 메타문자: 쉘 명령에서 사용하는 문자들 중 사전에 정의된 특별한 기능대로 동작하는 일부 문자(https://eunguru.tistory.com/91)
- popen: 한 프로그램 프로세스에서 다른 프로세스로 정보를 전달(파이프=연결통로)
- (https://nasmbit.tistory.com/38)
- 리스트로 각각 지정하면 인젝션 방지가능?
- 기능에 해당하는 라이브러리 사용
- ping3는 소켓프로그래밍을 통해 ping기능을 구현한 라이브러리입니다.
- 사용하고자하는 기능을 OS 커맨드가 아닌 구현한 라이브러리로 대체 사용할 수 있습니다.
라이브러리의 보안성 및 안정성 등을 검토한 후 사용하여야 합니다.
Server Side Template Injection(SSTI)
웹 어플리케이션에서 동적인 내용을 HTML로 출력할 때 미리 정의한 Template에 동적인 값을 넣어 출력하는 Template Engine을 사용하기도 합니다. 내 정보를 출력하는 페이지를 예로 들면 오른쪽 탭의 코드처럼 Template을 만들고 변수를 넣어 동적으로 HTML을 만들 수 있습니다.
--Server Side Template Engine
서버에서 DB 혹은 API에서 가져온 데이터를 미리 정의된 Template에 넣어 html을 그려서 클라이언트에 전달해주는 역할을 한다.
즉, HTML 코드에서 고정적으로 사용되는 부분은 템플릿으로 만들어두고 동적으로 생성되는 부분만 템플릿 특정 장소에 끼워넣는 방식으로 동작할 수 있도록 해준다.
--Client Side Template Engine
html 형태로 코드를 작성할 수 있으며, 동적으로 DOM을 그리게 해주는 역할을 한다.
Javascript 라이브러리로 랜더링이 끝난뒤 (즉, HTML Dom이 다 그려진 뒤)에 서버 통신 없이 화면 변경이 필요할 경우
https://gmlwjd9405.github.io/2018/12/21/template-engine.html
만약 Template 내부에서 사용되는 context가 아닌 Template source에 사용자 입력이 들어가게 된다면 악의적인 입력을 통해 개발자가 의도하지 않은 임의의 Template 기능을 실행할 수 있습니다.
즉, 사용자의 입력 데이터가 Template에 직접 사용될 경우 Template Engine이 실행하는 문법을 사용할 수 있기 때문에 SSTI 취약점이 발생하게 됩니다.
SSTI 취약점을 막기 위해서는 사용자의 입력 데이터를 Template source에 삽입되지 않도록 해야합니다. 사용자의 입력 데이터를 Template에서 출력하기 위해서는 Template context에 값을 넣어 출력해야 합니다.
각 언어별 사용되는 Template Engine
Python | Jinja2, Mako, Tornado ... |
PHP | Smarty, Twig, ... |
JavaScript | Pug, Marko, EJS ... |
-jinja2
from jinja2 import Environment, PackageLoader
env = Environment(loader=PackageLoader('yourapplication', 'templates'))
template = env.get_template('template.html')
print template.render(msg="Hello World!")
-template.html
<!DOCTYPE HTML>
<html>
<head></head>
<body>
{{ msg }}
</body>
</html>
(https://spoqa.github.io/2012/06/27/using-jinja2-api.html)
content는 그대로 입력된다.
secret객체에 admin의 비번이 있다.
템플릿 엔진에서 data는 {{ }}안에 변수를 넣어 사용한다.
ex) {{name}}
{{age}}
{{company}}
secret.password에 {{secret.password}} 하면 된다.
내용에 content={{secret.password }} 포함하기 >> secret.password 알 수 있음!
<Path Traversal>
Path가 사용되는 대표적인 로직으로는 URL/File이 있습니다. URL/File의 Path에는 Parent Directory(상위 디렉터리)를 의미하는 구분자(..)가 있습니다. 예를 들어 /tmp/test/../a 경로가 해석되면 /tmp/test/의 상위 폴더인 /tmp/폴더의 하위에 있는 a 파일, 즉 /tmp/a를 나타냅니다.
사용자의 입력 데이터가 적절한 검증 없이 URL/File Path에 직접적으로 사용될 경우 설계 및 개발 당시에 의도하지 않은 임의의 경로에 접근할 수 있는 Path Traversal 취약점이 발생하게 됩니다.
URL 구분 문자를 사용하지 못하도록 하는 필터링 또는 인코딩이 부재하다면 ../과 같은 구분 문자를 통해 의도한 경로가 아닌 상위 경로에 접근해 다른 API를 호출할 수 있습니다.
URL에서 사용하는 구분문자
문자 의미
/ | Path identifier |
.. | Parent directory * /tmp/test/../1234 => /tmp/1234 |
? | Query identifier * ? 뒤는 query로 해석 |
# | Fragment identifier * # 뒤의 값은 Server로 전달되지 않음 |
& | Parameter separator : 구분자 * key1=value&key2=value... 형식으로 사용 |
https://internal.dreamhack.io/dreamhack/path/name/../passwd/info
>>
웹 어플리케이션에서 해석되는 경로:/dreamhack/path/passwd/info
경로 조종 가능
&name="lol" 이렇게 파라미터 집어넣을 수도 있다.
get_info함수는 /api/user/ + username이다. make_admin이 /api/admin/make_admin/username이므로
../admin/make_admin/guest 을 칸에 쓰면 admin 이 된다.
SSRF ( Server-side Request Forgery)
웹 어플리케이션에서 사용자가 입력한 URL에 요청을 보내는 기능이 구현되어야 하는 경우도 있습니다. 예를 들어 URL을 통해 사용자가 입력한 사진을 업로드하는 기능을 구현하면 사용자가 입력한 URL을 웹 어플리케이션에서 접근해야합니다.
"Client-side Basic" vs Cross-site Request Forgery(CSRF)
변조된 요청을 보내는 대상의 차이
CSRF는 변조된 요청이 웹 클라이언트(브라우저)가 보내며, SSRF는 웹 어플리케이션에서 보내지게 됩니다.
웹 어플리케이션에서 요청을 보내기 때문에 웹 어플리케이션이 작동하고 있는 서버 내부의 포트, 서버와 연결된 내부망에 요청을 보낼 수 있고 Server-side에서 변조된 요청 / 의도하지 않은 서버로 요청을 보내는 공격이 SSRF입니다.
웹 서비스 인프라를 구성할 때 외부망/내부망을 나누어 설계하고 "내부망에서는 인증된 서버/사용자만이 요청을 보낼 수 있다"고 가정해 별도의 인증 없이 기능을 구현하는 경우가 많이 있습니다. 클라우드 플랫폼들에서도 내부망에서 작동하는 기능들이 있어 SSRF 공격이 발생하면 인프라를 공격하는 취약점이 될 수 있습니다.
SSRF 취약점을 방지하기 위해서는 사용자가 입력한 URL의 Host를 화이트리스트 방식으로 검증하는 방법이 있습니다. 미리 신뢰할 수 있는 Domain Name, IP Address를 화이트리스트에 등록하고 사용자가 입력한 URL에서 Host 부분을 파싱해 화이트리스트에 있는지 확인하여 사용합니다.
자신의 IP주소를 손수 입력해도 괜찮지만, 다른 클라이언트에게 내 서버의 접속경로를 알려주는 용도가 아니면
localhost 혹은 127.0.0.1을 사용한다. (https://suzxc2468.tistory.com/178)
admin주소로 admin또는 config 실행시킬 수 있다.
http://127.0.0.1/admin
http://127.0.0.1/config 이 결과값으로
-->Config Page
AdminIP: 127.0.0.1
SecretKey: XXXX-XXXX-XXXX-XXXX
internal-api-URL: http://internal-api.dreambank
내부 api 주소를 알 수 있다.(내부 서버 주소)
@app.route("/user")
def user():
action = request.args.get('action')
if action == "grant": name = request.args.get('name')
result = userLevel(name, 'admin')
elif action == "list": result = userList()
else: result = userList() return result
사용자 목록 확인 URL http://internal-api.dreambank/user
action이 없으면 리스트 반환
사용자 권한 부여 URL http://internal-api.dreambank/user?action=grant&name=dream
dream 사용자를 admin으로
"grant"가 아닌 이유는 request.arg.get은 문자열을 반환하기 때문
File 취약성
- 파일 업로드 취약점
- 서버의 파일 시스템에 사용자가 원하는 경로 또는 파일 명 등으로 업로드가 가능하여 악영향을 미칠 수 있는 파일이 업로드되는 취약점입니다.
- 파일 다운로드 취약점
- 서버의 기능 구현 상 의도하지 않은 파일을 다운로드할 수 있는 취약점입니다.
파일 업로드 취약점
사용자가 파일 업로드 시 파일의 내용과 함께 파일의 이름도 함께 서버로 전송됩니다. 이때 서버가 아무런 검증이 없다면 서버의 파일 시스템에 원하는 파일을 저장할 수 있습니다.
<form 태그 속성>
method : 전송 방식 - post , get
action : 전송 목적지 - url
enctype : 전송 데이터 형식 설정(encoding type)
enctype은 encoding type 을 말하는데 종류는 세가지 있다.
1. application/x-www-form-urlencoded
디폴트 값은 이거다. 서버로 전송되기 전에 url-encode 된다는 뜻.
2. mutipart/form-data
파일 받으면서 설정해준게 이 값인데 이미지나 파일을 서버로 전송할 경우 이 방식을 사용한다고 한다.
3. text/plain
이 형식도 많이 봤을 듯 한데
이건 인코딩을 하지 않은 문자 그대로의 상태를 전송한다는 의미. (https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=freakout1242&logNo=143392199)
if __name__ == '__main__'
app.run()
__name__ == __main__은 인터프리터에서 직접 실행했을 경우에만 if문 내의 코드를 돌리라는 명령이다.
파일이름 test.txt
파일이름 ../hack.py로 해서 uploads 위의 디렉토리로 파일 다운로드되었다.
공격에 앞서 웹 서버의 CGI라는 개념을 이해하여야 합니다.
CGI(Common Gateway Interface)는 사용자의 요청을 받은 서버가 동적인 페이지를 구성하기 위해 엔진에 요청을 보내고 엔진이 처리한 결과를 서버에게 반환하는 기능을 말합니다.
예를 들어 웹 서버는 Apache, 웹 어플리케이션은 php를 사용하는 서버에 사용자가 요청을 보내면 Apache는 사용자의 요청을 해석하고, 사용자가 요청하는 리소스의 확장자가 .php와 같이 php엔진을 사용하기로 설정되어 있는 확장자라면 mod_php(CGI)를 통해 사용자의 요청을 php 엔진이 처리 및 실행하도록 요청합니다.
apache는 사용자의 요청을 해석한다.
.php확장자 외에도 .php3/4/5/7, .pht, .phtml 등의 확장자를 가진 파일이라면 php에서 처리되도록 핸들링한다.
<FilesMatch>지시문 은 지시문과 마찬가지로 파일 이름으로 묶인 지시문의 범위를 제한합니다.
그러나 정규 표현식을 허용합니다 .
예를 들면 :< FilesMatch ". + \. (gif | jpe? g | png) $" > # ... </ FilesMatch >
즉, php, jsp, asp 등과 같이 CGI를 통해 서비스를 하는 형태에서는 확장자를 통해 웹 어플리케이션 엔진에 요청 여부를 판단합니다.
웹 서비스가 동작하는 경로에 사용자가 원하는 파일 내용과 파일 명을 업로드 할 수 있다면 서버가 엔진에 요청하는 확장자를 업로드하여 서버의 웹 어플리케이션에 원하는 코드를 실행 할 수 있습니다.
웹 서버가 파일을 실행하는 시기는 사용자의 요청이 들어오면 파일 시스템에서 해당 파일을 찾아 실행하는 것이기 때문에 웹 서버가 서비스하는 경로에 파일을 업로드할 수 있어야 합니다.
웹 어플리케이션이 실행하는 코드를 악의적인 공격자가 조작할 수 있다면 웹 어플리케이션 언어에 내장된 OS 명령어 등을 사용할 수 있으며, 해당 서버의 쉘을 웹을 통해 사용한다고하여 WebShell(웹쉘)이라는 악성코드가 등장하게 되었습니다.
변수의 선언
PHP에서는 달러($) 기호를 사용하여 다음과 같이 변수를 선언합니다.
$변수이름 = 초깃값;
(http://tcpschool.com/php/php_basic_variable)
PHP의 변수는 변수의 맨 앞에 $를 붙이는 것이 특징입니다. 예를 들어, $age나 $TEL과 같이 작성합니다.
$_사용이유:
문자와 숫자, 밑줄(_)로 구성되지만, 숫자로 시작할 수 없다. (문자로 시작하지 않는 변수일때)
변수의 첫 글자는 문자 또는 밑줄(_)입니다. $19years는 안되지만, $_19years는 허용됩니다.출처: https://recoveryman.tistory.com/206 [회복맨 블로그]
_FILES :
* 예제 스크립트에서 $_FILES의 내용은 다음과 같습니다. 위 예제 스크립트에서 사용한 파일 업로드 이름 userfile로 표현함에 주의하십시오. 어떠한 이름이라도 가질 수 있습니다.
$_FILES['userfile']['name']
클라이언트 머신에 존재하는 파일의 원래 이름.
$_FILES['userfile']['type']
브라우저가 이 정보를 제공할 경우에, 파일의 mime 형식. 예를 들면 "image/gif".
$_FILES['userfile']['size']
업로드된 파일의 바이트로 표현한 크기.
$_FILES['userfile']['tmp_name']
서버에 저장된 업로드된 파일의 임시 파일 이름.
$_FILES['userfile']['error']
파일 업로드에 관련한 에러 코드. ['error']는 PHP 4.2.0에서 추가되었습니다.
(https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=questzz&logNo=220255928163)
empty( $var ): $var가 비어있는지 검사하고, 비어있다면 TRUE, 비어있지 않다면 FALSE를 반환합니다.(empty시 true)
if(!empty($_FILES['file'])): 비어있지 않다면
generateRandomString() : 랜덤 문자열 생성 (https://blog.devez.net/ko/285)
ext:extention(확장)의 줄임말
pathinfo() 함수는 파일 경로에 대한 정보를 반환하는 함수
(https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=crehacktive3&logNo=221009840025)
bool move_uploaded_file ( string $filename , string $destination )
move_uploaded_file()은 서버로 전송된 파일을 저장할 때 사용하는 함수입니다.
출처: https://zzaps.tistory.com/52 [궁극의 잡 블로그]
php 처리 문법
<?php system("ls");?>
포함시키거나
xss 취약점 이용
<script>alert(1);</script>
*php문법: http://tcpschool.com/php/php_intro_syntax)
File Downloads
사용자가 업로드한 파일을 다른 사용자와 공유하기 위한 파일 다운로드 기능이 존재합니다.
파일 다운로드 기능을 구현하는 방법은 여러가지지만 취약점이 발생하는 가장 흔한 형태는 사용자가 입력한 파일이름을 검증하지 않은 채 그대로 다운로드를 제공하는 행위입니다. 아래는 취약한 코드 예시입니다.
filename = request.args.get("filename", "")
filename에 해당하는 값이 없는 경우 두번째""값을 이용한다
(https://tariat.tistory.com/755)
open( ) : 파일 불러오기
불러온 파일을
rb: 읽기모드로 파일 열기, 바이너리 모드
f.read()처럼 불러온 파일 읽기
개발자는 사용자가 uploads 디렉토리 안에 존재하는 파일만 다운로드할 수 있을 것이라고 생각할 수 있지만, Injection 주제에서 배운 Path Traversal을 이용하면 uploads 경로보다 더 상위 경로에 존재하는 시스템 파일, 설정 파일과 같은 중요한 정보들을 다운로드할 수 있습니다.
http://example.com/download?filename=docs.pdf 을
http://example.com/download?filename=../../../../etc/passwd
이렇게 하면 비밀번호 파일 다운로드 가능
파일 다운로드 취약점을 막기 위해서는 기본적으로 인자에 다운로드 받으려는 파일의 경로나 이름을 넘기지 않는 것이 좋습니다. 반드시 이름을 넘기는 방식으로 구현해야 한다면 상대경로로 접근하는데 사용될 수 있는 .. 과 / 와 \\ 를 적절하게 필터링 해야합니다.
다음으로 권장되는 방법은 데이터베이스에 다운로드 될 파일의 경로와 그에 해당하는 랜덤 키를 생성해 1대 1로 매칭해서 저장해두고 해당 랜덤 값이 인자로 넘어왔을 때 데이터베이스에 존재하는 파일인지를 먼저 식별하고 다운로드 하는 것이 안전합니다. 아래는 예시로 작성한 예제 코드입니다.
파일에 랜덤한 file_id를 이용한 파일 경로를 할당하여 파일 경로가 있어야만 파일을 열 수 있다.
다운받으려는 파일의 경로에서 단순히 ../ 만을 필터링 하려고 filename = filename.replace("../", "") ../를 삭제
과 같이 상위 경로로 올라가는 키워드를 없애면 상위 경로로 올라가지 못하기 때문에 안전할 것이라고 생각할 수 있으나
공격자는 ..././file 과 같은 형식으로 요청해 ../가 삭제되어 다시 새로운 ../를 만드는 형식으로 우회할 수 있습니다.
또한 만약에 웹 서버가 동작하는 운영 체제가 윈도우 운영체제일 경우 ../외에 ..\\로도 상위 경로에 접근할 수 있기 때문에 둘 다 필터링 하는 것이 필요합니다.
<Business Logic Vulnerability>
Business logic(비즈니스 로직)은 규칙에 따라 데이터를 생성·표시·저장·변경하는 로직, 알고리즘 등을 말합니다. 예를 들어 게시판 서비스는 회원 가입/로그인, 게시물 작성/수정/삭제 등 다양한 로직이 모여 하나의 서비스가 완성됩니다.
Business Logic Vulnerability(비즈니스 로직 취약점)은 정상적인 비즈니스 로직을 악용하는 것을 의미합니다.
인젝션, 파일 관련 취약점들은 사용자의 악의적인 데이터가 서버의 시스템 상에서 악영향을 미치는 공격을 수행하지만, 비즈니스 로직 취약점은 서비스의 기능에서 적용되어야 할 로직이 없거나 잘못 설계된 경우 발생하게 됩니다.
게시물 수정을 위한 비즈니스 로직 예시입니다.
- 게시물 수정 비즈니스 로직
1. 사용자가 게시물 수정을 요청한다.
2. 로그인된 사용자인지 확인한다.
3. 수정을 요청한 사용자가 해당 게시물을 수정 할 수 있는 권한인지 확인한다
.4. 2,3번 과정이 확인되면 데이터베이스에 사용자가 입력한 정보로 수정한다.
위와 같은 상황에서 3번 과정이 설계 과정의 실수로 인해 누락될 경우 악의적인 공격자는 다른 사용자의 게시물도 수정할 수 있는 비즈니스 로직 취약점이 발생하게 됩니다.
- Business Logic Vulnerability
- 정상적인 흐름에서 검증 과정의 부재 및 미흡으로 인해 정상적인 흐름이 악용되는 취약점입니다.
- IDOR (Insecure Direct Object Reference)
- 변조된 파라미터 값이 다른 사용자의 오브젝트 값을 참조할 때 발생하는 취약점입니다.
- Race Condition
- 비즈니스 로직의 순서가 잘못되거나, 한 오브젝트에 여러 요청이 동시에 처리되는 상황에서 발생하는 취약점입니다.
Insecure Direct Object Reference (IDOR) 취약점은
안전하지 않은 객체 참조라는 의미와 같이 객체 참조 시 사용하는 객체 참조 키가 사용자에 의해 조작됐을 때
조작된 객체 참조 키를 통해 객체를 참조하고, 해당 객체 정보를 기반으로 로직이 수행되는 것을 의미합니다.
IDOR 취약점이 발생하는 주된 원인은 사용자의 입력 데이터에 의해 참조하는 객체가 변하는 기능에서 사용자가 참조하고자 하는 객체에 대한 권한 검증이 올바르지 않아 발생합니다.
DOR 취약점을 방어하기 위해서는 객체 참조 시 사용자의 권한을 검증하는게 가장 중요합니다. 즉 사용자가 의도한 권한을 벗어나서 행동 할 수 없도록 권한 등을 분리해서 관리하여야 합니다.
또한 로그인 등의 기능을 통해 사용자 인증을 거친 후 사용하는 서비스에서는 사용자 식별을 위한 정보를 사용자의 입력 데이터로 구분하기 보다는 사용자가 요청 시 전달하는 세션을 통해 서버 내에서 처리하는 것이 안전합니다.
그 외에도 객체를 참조하기 위해 사용하는 객체 참조 키를 단순한 숫자가 아닌 무작위 문자 생성 등을 통해 악의적인 공격자가 객체에 참조하기 위한 객체 참조 키를 추측하기 어렵게 만드는 방법 등이 있습니다.
해쉬 함수를 이용해 객체 참조 키를 사용할 경우 솔트(Salt)를 사용해 해쉬 함수를 안전하게 사용해야 합니다.
<Race Condition>
Race Condition(레이스 컨디션)은 공유 자원 처리 과정에서 해당 자원에 대한 동시 다발적인 접근으로 인해 발생하는 취약점입니다.
웹 해킹에서는 데이터베이스 또는 파일 시스템과 같이 웹 어플리케이션에서 공유하는 자원들에 대한 접근 과정에서 데이터를 참조하는 타이밍의 차이로 인해 취약점이 발생하게 됩니다.
주로 검증 과정에서의 데이터와 수정 과정의 데이터의 차이로 인해 웹 어플리케이션에서 의도하지 않은 흐름으로 진행하게 됩니다. 또한 비즈니스 로직의 잘못된 순서로 인해 취약점이 발생하기도 합니다.
예시: 빠르게 물건 사기 버튼을 클릭한다면 재고 반영 데이터보다 빨라서 비용 이상이 구매되는 경우
레이스 컨디션 취약점으로 부터 안전하게 보호되어야하는 로직들은 하나의 접근이 끝난 후 다음 접근을 처리하도록 쓰레드 락 등을 통해 동시 다발적인 접근을 방지하여야 합니다.
또한 레이스 컨디션 취약점을 발생시키기 위해 다량의 접근이 필요하다는 점을 이용하여 특정한 행위에 요청을 할 시에는 CSRF 토큰, 캡차 등을 통해 다량의 접근을 방지하는 방법을 통해 웹 어플리케이션을 보호할 수 있습니다.
<Language Specific Vulnerability>
해당 언어만의 특성, 함수에서 발생할 수 있는 취약점
Common
- 코드 실행 함수 (eval)
- OS command function
- Filesystem function
- serialize / deserialize
PHP
- include
- Wrappers
- extract
- Type Juggling
- Comparison
- session
- Upload Logic
Javascript
- Comparison Problem
- Prototype Pollution
자바스크립트는 클라이언트 사이드에서는 웹 브라우저의 내장 언어로 사용되며, 서버 사이드에서는 자바스크립트를 기반으로 만들어진 NodeJS로 사용될 수도 있습니다.
<eval 함수>
- php
<?php
eval("1+1"); // 2
?>
- python
eval("1+1") # 2
exec("2+2") # 4
# eval은 하나의 식만을 처리하지만, exec은 하나의 문장을 처리합니다.
(eval은 식을 문자열로 입력받음. exec은 문장(a = a+4)을 입력받고 따로 반환값 없을 수 있다.)
eval("a=1") # SyntaxError: invalid syntax
exec("a=1") # a=1
- javascript
eval("1+1"); // 2
eval의 인자로 사용자의 입력 데이터가 사용될 경우 악의적인 데이터를 입력해 원하는 프로그램 코드를 실행할 수 있기 때문에 사용자의 입력 데이터가 eval의 인자로 사용되지 않아야 합니다.
OS Command Function
OS Command를 실행하기 위한 어플리케이션 함수 사용 시 실행하는 명령어에 사용자의 입력 데이터가 포함될 경우 Command Injection 취약점이 발생할 수 있습니다.
- Python
- os.system, popen
- subprocess.call, run, Popen
- PHP
- system, passthru
- shell_exec, backtick operator (e.g. `ls`)
- popen, proc_open
- exec
- Javascript (Nodejs)
- child_process.exec, spawn
Filesystem function
어플리케이션에서 파일 시스템에 접근할 수 있는 함수들의 인자가 사용자의 입력 데이터 또는 변조될 가능성이 있는 변수들을 사용할 경우 서버의 파일 시스템을 공격하거나, 다른 취약점으로 악용될 수 있습니다.
파일 시스템을 공격하여 발생할 수 있는 대표적인 서버의 피해는 아래와 같습니다.
서버 또는 어플리케이션의 환경, 상황에 따라 발생할 수 있는 문제는 각각 다를 수 있습니다.
- File Read
- 어플리케이션 코드, 설정 파일 정보 등의 노출
- File Write
- WebShell 생성을 통한 원격 코드 실행 공격
- 기존 설정 파일을 덮는 공격을 통해 운영체제 또는 어플리케이션 설정 변경
- Etc
- 파일 복사를 통해 File Write와 유사한 상황 발생
- 설정 파일을 삭제하여 운영체제 또는 어플리케이션 서비스 무력화
php fopen($filename, "r") : 읽기 모드로 파일 열기
FILE* fopen (const char* fileName, const char* fileMode)
함수의 이름을 보면 f + open 인데요.
f는 file을 말합니다. 즉 file open, “파일을 열어라”라는 함수 입니다. 명확한 이름이죠?
첫번째 매개변수 : fileName = 파일의 이름 (경로 포함)
두번째 매개변수 : fileMode = 파일을 어떤식으로 오픈할건지, 읽기만 할건지 쓰기만 할건지 둘다할건지.
출처: https://blockdmask.tistory.com/392 [개발자 지망생]
highlight_file: 구문 강조하기
mixed highlight_file ( string $filename [, bool $return = false ] )
PHP 용 내장 구문 형광펜에 정의 된 색상을 사용하여 filename에 포함 된 구문 강조 표시 버전을 인쇄하거나 반환합니다.(https://sir.kr/pg_tip/15432)
include $filename : 파일을 가져와서 include사용한 파일에 내용 복사해 붙여넣기(https://sergeswin.com/115/)
해석해서 실행하는 기능도 있다고 함
직렬화/ 역직렬화
serialize(직렬화)는 Object 또는 Data의 상태 또는 타입을 특정한 형태의 포맷을 가진 데이터로 변환하는 것을 의미합니다.
deserialize(역직렬화)는 직렬화된 데이터를 원래의 Object 또는 Data의 상태 또는 타입으로 변환하는 것을 의미합니다.
직렬화는 오브젝트, 데이터의 현재 상태와 타입들을 저장하고, 원하는 상황에 역직렬화를 통해 동일한 상태와 타입을 가진 오브젝트 또는 데이터들을 사용할 수 있습니다.
공격자는 역직렬화 과정에서 어플리케이션 상에서 다른 행위를 발생시키는 상태 또는 타입을 이용하여 악의적인 행위를 발생시키거나, 특정한 상황에서 호출되는 메소드들을 이용하여 공격에 사용합니다.
__init__(self, a, b): //생성자
self.A = a
self.B = b
(https://www.inflearn.com/questions/4642)
pickle.dump(): 파이썬 객체를 직렬화하여 이진 파일(binary file)에 저장하기
문자열 뿐만 아니라 리스트, 정수, 바이트 모두 형태 변형없이 저장 가능하다.
출처: https://rfriend.tistory.com/525 [R, Python 분석과 프로그래밍의 친구 (by R Friend)]
dump()를 이용하여 저장한 파일은 loads로 읽어와야한다.
\x는 16진수임을 뜻한다.
pickle 모듈 - 파이썬
dump 메소드 : 객체를 파일에 저장
load 메소드 : 파일에서 객체를 읽어오기(원래값으로)
출처: https://dojang.io/mod/page/view.php?id=2327
참고 (파이썬 클래스) https://wikidocs.net/28
파이썬
파이썬에서 직렬화/역직렬화를 사용하는 대표적은 모듈은 pickle과 yaml이 존재합니다.
주로 Python Object의 __reduce__ 메소드를 공격에 사용합니다.
reduce : 누적합계 함수
참고: https://codepractice.tistory.com/86
NodeJS
참고: nodejs - node-serialize https://heewon26.tistory.com/51
외부 모듈 가져올 때 require사용
PHP
call_user_func ( callable $callback , mixed ...$args ) : mixed
첫 번째 매개 변수가 제공 한 callback 호출하고 나머지 매개 변수를 인수로 전달합니다.
PHP deserialize 공격에서는 Object의 특수한 상황에서 실행되어지는 Magic Methods를 이용합니다.
주로 사용하는 Magic Methods는 아래와 같습니다.
__destruct() ==> 소멸자의 개념으로 오브젝트 소멸 시 호출됨.__wakeup() ==> 역직렬화 시 호출됨.
magic method 예시 ---> 사이트 들어가서 보기
PHP 취약점
<Include>
PHP의 include 함수는 인자로 전달된 파일을 읽은 후 해당 파일의 내용을 출력합니다.
파일의 내용 중 PHP 코드로 해석되는 구문이 존재하면 해당 코드를 실행합니다.
- php 코드로 해석되는 구문
<?
php {php code}
?>
<?={php code}?> # short echo tag; php코드의 결과가 출력됩니다.
include 함수는 동적으로 다른 PHP 페이지를 로드해야할 때 주로 사용합니다.
페이지의 배경이나 기본 레이아웃은 고정시킨 채 내용의 변경이 필요한 경우 include를 통해 다른 php 파일을 포함시킬 수 있습니다. include와 비슷한 역할을 하는 함수로는 include_once, require, require_once 등이 있습니다.
소스코드의 구성이 위와 같을 때 /index.php?page=register.php 와 같은 식으로 요청을 보내면
동적으로 register.php를 로드해 사용자에게 회원가입 페이지를 보여줄 수 있습니다.
include 함수는 파일의 확장자 또는 파일의 타입과는 상관없이 파일의 내용에 php 태그가 포함될 경우 php 코드가 실행되기 때문에 사용 시 주의해야합니다.
include할 파일을 사용자의 입력 데이터에 의해 조작될 수 있다면 /index.php?page=/etc/passwd와 같이 요청하여 서버 내의 파일 정보를 출력하거나, 업로드할 수 있는 다른 기능과 연계하여 서버에서 원하는 PHP코드를 실행해 공격에 사용할 수 있습니다.
서버 로컬 파일을 Include 하는 취약점을 Local File Inclusion(LFI),
외부 자원을 Include 하는 취약점을 Remote File Inclusion(RFI)라 부릅니다.
PHP의 allow_url_include 설정은 외부 자원에 대한 접근을 방지하는 옵션입니다. 대부분의 기본 설정에서는 해당 옵션이 off된 상태입니다.
Wrapper
PHP는 include(), fopen(), copy(), file_exists(), filesize() 와 같은 파일 시스템 함수에서 URL style 프로토콜을 위한 wrapper들이 존재합니다.
파일 시스템 관련 함수로 filename이 사용자의 입력이 될 경우 php에 존재하는 wrapper를 사용해 개발자의 의도와 다른 행위를 발생시킬 수 있습니다.
- file:// — Accessing local filesystem
- http:// — Accessing HTTP(s) URLs
- ftp:// — Accessing FTP(s) URLs
- php:// — Accessing various I/O streams
- zlib:// — Compression Streams
- data:// — Data (RFC 2397)
- glob:// — Find pathnames matching pattern
- phar:// — PHP Archive
그 외의 Wrapper
https://www.php.net/manual/en/wrappers.php
php
php의 다양한 I/O stream과 filter를 사용합니다.
- php://stdin, php://stdout and php://stderr
- stdin, stdout, stderr로 연결합니다.
- php://fd/<fd number>
- file descriptor에 연결합니다.
- php://memory and php://temp
- 메모리에 연결합니다.
- php://input
- HTTP의 body 즉, POST 데이터를 입력받습니다.
- php://filter
- I/O 스트림에 특정 필터를 설정합니다.
이름 설명
read=<filter> | read 시 사용할 필터 |
write=<filter> | write 시 사용할 필터 |
resource=<stream name> | 필터링을 설정할 stream 정보 |
php에서 include시 php태그가 존재하면 php실행되고 해당 코드는 출력되지 않습니다.
하지만 base64등의 인코딩을 통해 php tag를 인코딩 시켜서 코드가 실행되지 않고 출력되도록하여 소스 코드를 노출시키는 방법을 많이 사용합니다.
Extract
PHP의 extract 함수는 배열에서 변수를 가져옵니다.
extract 함수 시 기존에 사용되고 있는 변수의 데이터를 덮을 수 있기 때문에 extract 함수에 사용자의 입력($_GET, $_POST, $_FILES)과 같은 신뢰할 수 없는 데이터가 사용되면 다른 변수를 변조하여 공격에 사용될 수 있습니다.
php extract 문서에서는 사용자의 데이터(입력)처럼 신뢰할 수 없는 데이터를 사용하지 않는 것을 권고하고 있습니다.
Type Juggling
PHP에서 서로 다른 타입인 변수를 비교 또는 연산 시 자동으로 형변환이 발생하며 의도치 않은 결과가 발생할 수 있습니다.
Comparison
서로 다른 타입이 비교 연산자에서 사용될 경우 type-juggling에 의해 의도치 않은 결과가 발생할 수도 있습니다.
비교 연산자(Equal, ==)를 사용할 때 비교하는 두 변수의 타입이 다르면 type-juggling에 의해 타입이 변환된 후 비교합니다.
정확한 비교를 하기 위해서는 동일한 타입인지도 비교하는 비교연산자(Identical, ===)를 사용해야합니다.
Example Name Result
$a == $b | Equal | type juggling 후 $a와 $b의 값이 같으면 True. |
$a === $b | Identical | $a와 $b의 타입과 값이 모두 같으면 True. |
$a != $b | Not equal | type juggling 후 $a와 $b의 값이 다르면 True. |
$a <> $b | Not equal | type juggling 후 $a와 $b의 값이 다르면 True. |
$a !== $b | Not identical | $a와 $b의 타입 또는 값이 다르면 True. |
PHP에서 문자열을 비교하는 strcmp 함수는 다음과 같은 형식으로 사용됩니다.
strcmp ( string $str1 , string $str2 ) : int
인자를 비교했을 때 같으면 0, $str1이 작으면 음수 $str2가 작으면 양수가 반환되고
string이 아닌 array를 인자로 전달할 경우 NULL을 반환합니다.
PHP에서는 NULL == 0 와 같은 식으로 비교하게 될 경우 단순 Equal 연산이기 때문에 Type Juggling이 발생해 0 == 0으로 비교하게 되어 문제가 발생할 수 있습니다.
<?php
var_dump(strcmp("a", array())); // NULL
var_dump(strcmp("a", array()) == 0); // NULL == 0 -> true
var_dump(strcmp("a", array()) === 0); // NULL === 0 -> false
strcmp 함수로 문자열을 비교할 때 Equal이 아닌 Identical(===)을 사용해야 합니다.
Session
PHP에서 session(세션)의 가장 기본적인 사용 형태는 다음 코드와 같습니다.
PHP의 기본 세션 관리는 다음과 같은 로직을 가집니다.
- session_start()
- HTTP Request에서 쿠키를 가져와 PHPSESSID(php 기본 Session ID, 쿠키를 통해 서버와 사용자가 주고받는 php session의 이름)의 값을 가져옵니다.
- PHPSESSID값이 존재하지 않으면 랜덤한 값의 PHPSESSID를 생성하고 세션 변수에($_SESSION) 빈 array를 할당합니다.
- PHPSESSID 값이 있으면 해당 PHPSESSID에 해당하는 값을 세션 변수에($_SESSION) 로드합니다.
- 2.$_SESSION 변수에 접근하여 값을 읽거나 변경합니다.
2. $_SESSION : 변수에 접근하여 값을 읽거나 변경합니다.
3. session_start로 시작된 해당 페이지(PHP)가 종료될 때 자동으로 세션 변수를 serialize한 값을 파일 시스템에 저장합니다.
- 기본적으로 파일 시스템의 {세션기본경로}/sess_{PHPSESSID값} 경로에 저장됩니다.
리눅스 시스템의 경우 기본 설정이라면 세션 기본 경로가 /var/lib/php/sessions 로 설정되는 시스템이 대부분입니다.
또한 세션에서 사용되는 키(PHPSESSID)를 사용자는 쿠키의 정보를 확인하여 알 수 있기 때문에 서버의 파일 시스템에서 세션 파일이 저장되는 경로와 파일을 추측해볼 수 있습니다.
세션에 사용자의 입력 데이터가 들어가게 된다면 다른 공격과 연계하여 사용될 수 있습니다.
예를 들어 include 함수의 인자를 조작 가능하고 인자에 대한 검증이 없는 페이지에서 세션 파일을 include 인자로 넘겨 원하는 PHP 코드를 실행할 수 있습니다.
session.php
위와 같은 파일이 존재할 때 /session.php?name=<?php system('id');?>와 같이 입력하여 php 코드를 세션에 삽입할 수 있습니다. (시스템의 세션아이디로 세션이 저장된다)
index.php
같은 서버에 include 취약점이 존재하는 경로가 존재한다면 /index.php?page=/var/lib/php/sessions/sess_d54ejehuaq8ksuoatpeph898q3와 같이 세션 파일을 include하여 세션 파일에 작성되어 있는 php 코드를 실행하는 공격이 가능합니다.
<Upload Logic>
아래 코드는 파일 업로드 기능을 수행하는 php 코드입니다.
위 코드에서 주목할 점은 사용자가 업로드한 파일을 서버 파일 시스템에 옮기는 move_uploaded_file 함수입니다.
php에서 파일을 업로드하면 서버에서 지정된 파일 시스템 경로로 바로 업로드 되는 것이 아니라 임시 디렉토리(리눅스 시스템에서는 기본적으로 /tmp를 사용함.)에 저장된 후 move_uploaded_file 함수에 의해 서버 코드가 설정된 경로로 파일을 옮기게 됩니다.
즉, php 파일 업로드 내부 로직은 다음과 같습니다.
- 임시 디렉토리에 임시 파일 생성
- php 로직 중 업로드 파일 처리 로직 존재 시 임시파일 참조($_FILES['file']['tmp_name'])
- php 로직 처리 후 임시 파일 삭제
php 코드에서 로직 처리등의 이유로 지연이 발생한다면 임시 파일은 지연이 발생하는 시간동안 존재하게 됩니다.
위 코드와 같이 시간 지연이 발생하는 페이지에 파일을 업로드한 후 해당 서버 임시 디렉토리를 확인하면 아래와 같이 임시 파일이 생성된 것을 확인할 수 있습니다.
임시 파일의 파일명 규칙은 php[a-zA-Z0-9]{6}입니다.
php 파일 업로드 내부 로직 문제 발생 가능점
- php 코드상에서 업로드 기능을 구현하지 않아도 사용자가 서버의 임시 디렉토리에 업로드 가능.
php의 기본 설정에서 사용자의 요청 중 파일이 존재한다면 코드상에서 처리하는 로직이 존재하지 않더라도 임시 파일을 생성합니다. 원하는 파일 경로나 파일 명으로 서버 파일 시스템에 존재하는 것은 아니지만, 원하는 파일 데이터가 서버의 예측 가능한 디렉토리(임시 디렉토리)에 작성될 수 있습니다.
또한 php 엔진의 문제를 발생시키는 Unhandled exception(php 웹 어플리케이션 코드 실행 중 php엔진의 문제가 발생하는 경우, segmentation fault)이 발생하게 되면 php 로직이 정상적으로 종료되지 않으며, php 파일 업로드 내부 로직의 마지막 과정인 임시 파일 삭제가 실행되지 않습니다. 삭제되지 않은 임시 파일은 특별한 행위가 발생하기 전까지 임시 디렉토리에 남아 있게 됩니다.
2. 예측 가능한 임시 파일 생성 규칙
임시 파일의 파일 명 생성 규칙은 php[a-zA-Z0-9]{6}입니다. 일반적으로 해당 파일 명을 한번에 찾기는 어렵지만, Unhandled exception를 이용하여 임시 디렉토리 내에 임시 파일이 삭제되지 않도록하고, 같은 파일 데이터를 가진 파일을 무한히 업로드하면 무작위 대입공격을 통해 충분히 예측 가능한 범위까지 줄일 수 있습니다.
파일 업로드 내부 로직에서 발생할 수 있는 문제점이 include 또는 파일 시스템에서 발생하는 취약점과 연계되면 서버의 명령어를 실행시키는 등의 공격으로 연계될 수도 있습니다.
Javascript 취약점
비교 문제
자바스크립트도 PHP와 마찬가지로 변수의 타입을 동적으로 정할 수 있는 Dynamic 언어입니다. 타입이 다른 두 변수의 값을 비교할 때 어떻게 되는지 알아보고 그 과정에서 발생할 수 있는 문제점을 알아봅시다.
자바스크립트는 두 값을 비교할 때 두 값 모두 객체가 아닐경우 valueOf, toString 함수를 이용해 원시 값(primitive value)을 가져와 비교하게 됩니다.
예제 코드의 2번 라인은 객체 a의 문자열 원시 값을 가져옵니다. 그 결과 [object Object]인 것을 확인할 수 있습니다.
4번 라인은 toString 함수를 dreamhack이라는 문자열을 반환하게 수정한 뒤 a 객체를 문자열과 비교합니다. 그 결과 true를 반환하는 것을 확인할 수 있습니다. 이를 통해 문자열과 비교할 경우 toString을 통해 문자열 원시 값을 가져온다는 사실을 알 수 있습니다.
7번 라인에서는 toString 대신 valueOf 함수를 dreamhack2로 반환하는 함수로 수정합니다. 이번에는 dreamhack2와 비교를 해보면 마찬가지로 true를 반환하는 것을 볼 수 있습니다.
이를 통해 toString보다 valueOf 함수가 원시 값을 구하기 위해 먼저 호출된다는 것을 알 수 있습니다.
배열의 원시값
예제는 우리가 임의 값을 반환하게 함수를 직접 수정했습니다. 이번에는 배열은 기본적으로 어떠한 문자열 원시 값을 반환하는지 알아보겠습니다.
A 값을 수정해 3번 라인에서 익셉션이 발생하게 할 수 있습니다. 배열의 문자열 원시 값은 Array.join(array, ',')와 일치 합니다. 따라서 첫번째 요소가 secretValue인 배열을 A에 넣어주면 charAt을 찾지 못해 익셉션이 발생합니다.
"secretValue" 문자열의 첫번째값 "s"
비교문에서는 정상적으로 검증되었지만 (문자열과 배열로 타입은 다르지만 값은 같음)
이후 charAt함수호출 시 Array에는 존재하지 않는 함수이기 때문에 오류가 발생하게 됩니다.
(array에 charAt 못씀, 문자열만 가능)
Prototype Pollution
자바스크립트는 프로토타입을 사용하는 언어입니다.
객체를 생성할 때 초기화 할 프로토타입을 지정해주지 않으면 Object.prototype의 속성과 정의된 함수(method)를 상속 받습니다. 객체는 __proto__ 를 이용해 자신이 가르키고 있는 프로토타입에 접근할 수 있습니다.
또한 같은 프로토타입을 상속받은 모든 객체는 메모리 상 같은 주소를 가리키고 있습니다.
초기화 할 프로토타입을 지정해주지 않은 a와 b 객체의 __proto__는 동일한 것을 확인할 수 있습니다.
또한 Object.prototype 과 일치합니다.--> 프로토타입 안정해주면 Object.prototype
Prototype Chain Lookup
첫번째 라인에서 Object.prototype을 상속받는 기본 객체를 할당합니다.
x.__proto__ 에 a, b를 각 할당한 후 Object.prototype에 c를 할당합니다. 그 후 x를 프로토타입으로 하는 객체 y를 생성합니다. y의 프로토타입은 Object.prototype과 다르다는 것을 알 수 있습니다.
y.c를 출력했을 때 undefined가 아닌 2가 나오는 것을 통해
프로토타입에 원하는 속성이 없으면 계속해서 상위 프로토타입에서 찾는다는 것을 볼 수 있습니다.
즉, y 에 c가 없다면, y.__proto__ 에 c가 없다면, y.__proto__.__proto__의 c를 검색합니다.
x.__prototype__.a = 1337은 x.a가 1337이라는 것이 아닌 프로토타입 x를 가진 객체의 a값이 1337이라는 것이다.
console.log(x.a) // 0
z의 객체는 {}이므로 z.a의 값은 z의 Object로 같은 프로토타입인 a의 값이다 (?)
Bracket Notation
객체에 접근할 때 사용하는 방법은 .을 이용하거나 [xyz]를 사용하는 방법이 있습니다.
전자는 dot notation, 후자는 bracket notation이라고 부릅니다.
Bracket notation은 속성의 키를 문자열로 사용하기 때문에 변수를 넣거나 dot notation에서 사용할 수 없는 문자 (=, ', ")를 속성 키로 지정할 수 있습니다.
a.x와 a[x]는 같다.
실습 모듈처럼 객체의 프로토타입을 오염시켜 의도치 않은 결과를 발생시키는 것을 Prototype Pollution이라고 합니다.
Misconfiguration(잘못된 설정)
잘못된 설정(misconfiguration) 취약점은 웹 서버(apache, nginx), 데이터베이스 서버(mysql, postresql, mongodb), 캐시 서버(redis), 웹 프레임워크(django, spring), 컨테이너(Docker, K8S) 등 모든 웹 어플리케이션 계층에서 발생할 수 있습니다.
잘못된 설정으로 인해 공격자는 허가되지 않은 동작을 수행할 수 있습니다. 다른 웹 취약점처럼 소스 코드 상에 존재하는 복잡한 로직에 의해 발생하는 취약점이 아닌 간단한 설정 오류로 인해 발생합니다.
따라서 검수할 때는 기본 메뉴얼에 존재하는 룰을 잘 따랐는지만 확인하면 돼 자동화된 도구로 발견하기 쉽습니다.
잘못된 설정이 발생하는 원인을 총 4가지로 나누었습니다.
부주의로 인해 발생하는 문제점
메뉴얼에 존재하지 않은 설정이거나 개발자의 부주의로 인해 설정 유무를 알지 못한 상태에서 서비스를 할 경우 자주 발생합니다.
- 권한 설정 문제
- 잘못된 권한 설정
- 기본 계정
- 기본 서비스
- 임시/백업 파일, 개발 관련 파일
편의성을 위한 설정에 의해 발생하는 문제점
개발자를 위한 설정이 서비스 환경에서도 켜져있어 공격자가 이를 통해 시스템 내부의 정보를 알아내 추가적인 공격에 사용될 수 있습니다.
- DEBUG / Error Message Disclosure
- 0.0.0.0으로 바인딩된 네트워크 설정
메뉴얼과 실제 구현체의 차이로 인해 발생하는 문제점
메뉴얼에 설명되어있는 대로 사용했지만 중의적 표현이 존재하거나 잘못 설명되어있을 때 발생합니다.
- NGINX ALIAS Path Traversal 취약점
해당 코드나 설정에 대한 이해 없이 사용해 발생하는 문제점
취약점이 존재하는 예제코드, StackOverflow 답변등을 이해 없이 복사 붙여넣기 해 사용하거나 메뉴얼에 존재하는 권고 설정을 무시한 채 사용해 발생합니다.
- https://stackoverflow.com/questions/47404893/reverse-image-proxy-without-specifying-host
- 모든 도메인을 허용한 CORS 설정
권한 설정 문제
웹 서비스를 구성하는 요소들 운용 시 시스템 또는 어플리케이션 상에서 권한 설정이 잘못된 경우에 대해서 설명합니다. 대표적인 권한 설정 문제는 아래와 같습니다.
- 기본 계정
- 프레임워크 등에서 기본적으로 제공하는 계정을 삭제하지 않아 문제가 발생할 수 있습니다. 프레임워크 등을 통해 제공되는 계정 정보는 ID/PW 등의 정보가 메뉴얼 등을 통해 공개되어 있기 때문에 이를 통해 특별한 취약점을 사용하지 않고 권한을 획득할 수 있습니다.
- 잘못된 권한 설정
- 웹 서버를 운용 하는 시스템 또는 어플리케이션 상에서 필요 이상의 권한을 부여하는 경우 문제가 발생할 수 있습니다.
- 서비스를 운용하는 OS의 사용자 권한이 일반 유저 또는 시스템 운용을 위해 생성된 계정을 사용하지 않고 superuser(root) 권한으로 사용할 경우 서비스의 로직이 superuser권한으로 수행됩니다.
- e.g. 웹 서버가 superuser 권한으로 동작하며, 파일 관련 취약점이 존재할 경우 일반 사용자 권한이 접근하지 못하는 시스템 파일등에도 접근이 가능하게 되며, 이를 통해 시스템 장악 등의 문제로 연계될 수 있습니다.
- DB, Docker 등의 어플리케이션 운용 시에도 해당 어플리케이션의 사용자 권한을 필요 이상의 권한을 부여할 경우 문제가 발생할 수 있습니다.
- e.g. DBMS내에서 root 계정을 사용하게 되면 기본적으로 모든 데이터베이스에 접근이 가능하며, 일반 유저가 사용하지 못하는 함수들도 사용할 수 있습니다. 만약 sql injection 취약점이 발생하게 되면 root 권한으로 사용할 수 있는 함수들을 통해 다른 취약점으로 연계할 수 있는 위협이 존재할 수 있습니다.
기본 서비스
서비스 요소들 운용 시 사용하는 시스템 또는 어플리케이션에서 개발자가 설정하지 않아도 기본적으로 제공되는 기능에 의해 문제가 발생할 수 있습니다.
대부분 개발 당시에 필요한 기능이거나, 내부적으로 사용해야 하는 기능들입니다. 즉, 운영 시에 의도하지 않은 경로 또는 기능이 노출되는 위협이 발생할 수 있습니다.
- 관리 서비스
- Tomcat - Web Application Manager
- Docker Registry API
- 모니터링 서비스
- Spring Boot Actuator - metrics
- Apache mod_status
Debug
디버그 모드 설정 또는 디버그 목적으로 코드 상에서 특정 정보를 사용자에게 제공할 경우 해당 정보를 기반으로 서버의 환경 및 정보가 노출될 수 있습니다.
- 서버 환경설정 또는 프레임워크 등의 어플리케이션 구동 시 환경 설정의 Debug 옵션을 설정해 운용할 경우, 사용자의 입력 데이터에 의해 에러가 발생하게 되면 해당 에러에 대한 정보가 노출될 수 있습니다. 악의적인 공격자는 이런 에러 정보를 통해 서버를 공격하기 위한 기반 정보들을 획득합니다.
- Debug 모드가 설정된 코드
- 에러 발생 화면
debug 모드가 설정된 상태에서 에러가 발생하게 되면
위 그림과 같이 에러의 정보와 에러가 발생하는 서버 코드가 함께 노출되는 위협이 발생하기도 합니다.
Error Message Disclosure(에러 메세지 노출)
코드 상에서 사용하는 변수 또는 정보가 디버그 목적 등으로 사용자들에게 노출되는 경우도 있습니다.
디버그 목적으로 사용 후 삭제하지 않거나 그대로 사용할 경우 악의적인 공격자에게 정보가 노출될 수 있습니다.
임시/백업 파일
웹 서버 디렉토리내에 임시파일이나 백업파일이 존재한다면 디렉토리 스캐너를 통해 파일이 유출될 수 있습니다. 자주 사용되는 임시 파일과 백업파일은 swp, ~, bak, sql등이 있습니다.
hello.php 파일을 vim 에디터로 열게되면 .hello.php.swp 라는 임시 파일이 생성됩니다. 웹 서버는 .php 확장자를 php script로 인식해 실행 결과를 응답하지만 .swp 확장자는 바이너리 파일로 인식해 파일 내용 (소스 코드) 가 포함된 임시파일을 응답하게됩니다.
hello.php는 password가 일치하면 임의 명령을 실행해주는 php 파일입니다. 공격자는 소스코드에 정의되어 있는 this_is_secret_password를 알 수 없습니다.
하지만 개발자가 vim 에디터로 수정중에 생긴 .swp를 통해 소스코드를 획득할 수 있습니다.
.swp파일을 다운로드 받아 vim hello.php 명령어를 입력하게 되면 아래와 같은 메시지를 확인할 수 있습니다
R키 (Recover)를 눌러주면 복구를 진행할 수 있습니다.
복구가 되었다는 메시지와 함께 원본 소스코드를 획득한 것을 확인할 수 있습니다.
vim 에디터의 swp 임시 파일 외에도 공격자에게 악용될 수 있는 여러가지 파일들이 있습니다. 아래 표는 위험이될 만한 확장자와 설명입니다.
확장자설명
bak | 백업 파일, 대부분의 에디터에서 사용함 |
config | 설정 파일, 비밀 키들이 존재하는 경우가 많음 |
sql | sql schema 파일, 데이터 베이스 구조를 알아낼 수 있음 |
sh | shell script 파일 |
~ | bluefish 에디터 백업 파일 |
조치 방안
서비스하기 이전에 서버 및 어플리케이션의 설정 및 파일들을 점검하여 서비스와는 무관한 파일들을 제거해야 합니다.
또한, 위험한 확장자에 대한 요청이 들어올 경우 거부하는 방법으로 위협을 없앨 수 있습니다. 아래는 nginx에서 적용가능한 설정 입니다.
개발 관련 파일
버전 관리 도구(VCS)를 사용해 소스코드를 다운로드 받거나 웹 폴더내에서 직접 개발을 하다 보면 해당 VCS가 자동으로 생성하는 정보 파일이 남습니다.
git은 .git폴더가 생성되며 해당 폴더 내 트래킹 되고 있는 파일들의 소스 코드를 쉽게 획득할 수 있습니다.
init 과정 보다 git clone ..을 통해 저장소를 받아온 후 웹 서버가 바로 서빙되는 경우 발생하기 쉽습니다.
hg도 마찬가지로 .hg 폴더를 생성합니다.
위 두개를 포함한 다양한 VCS 도구가 생성하는 정보는
https://github.com/kost/dvcs-ripper 도구를 이용해 쉽게 진단할 수 있습니다.
조치 방안
임시/백업파일과 마찬가지로 서비스하기 이전에 점검하여 서비스와는 무관한 파일들을 제거해야 합니다. 또한, 웹 URL을 통해 접근하는 것을 차단하는 방법이 있습니다. 웹 서버의 설정으로 쉽게 막을 수 있습니다.
아래는 nginx 예시 설정입니다.
0.0.0.0 바인딩
내부 서비스 네트워크 또는 특정 네트워크에서만 접근할 수 있도록 접근 제어가 이루어져야 할 서버들이 개발 또는 운용상의 편의를 위해 0.0.0.0으로 바인딩 되어있다가 서비스 환경으로 변경되었음에도 바인딩 주소를 그대로 사용할 때 발생합니다. 이를 통해 악의적인 공격자는 인증이 없거나 취약한 내부 서비스에 접근할 수 있게됩니다.
다음은 웹 환경 개발시 자주 쓰이는 내부 서비스 입니다.
- 서비스 목록
- 이름 용도
redis cache 서버 mysql RDBMS 서버 k8s master node 클러스터 관리
bind 주소를 0.0.0.0으로 설정해 모든 IP Address로 부터 오는 요청을 허용하였습니다. 그 결과 공격자는 외부 인터넷 환경에서 해당 redis 서버에 접근할 수 있고 redis 기본 설정 상 인증 절차가 없기 때문에 접근만으로 임의 redis 명령을 수행할 수 있게 됩니다.
조치 방안
두 가지 방법으로 문제점을 해결할 수 있습니다.
첫 번째는 해당 방법을 이용해 허용할 포트를 제외한 설정은 모두 삭제하는 것입니다. 클라우드 환경은 방화벽 기능을 제공합니다.
위 방법은 간단하게 해결할 수 있지만 인스턴스 이미지를 복사해 방화벽이 허용된 곳에서 사용한다면 취약합니다.
두번째는 근본적인 원인을 해결하는 방법입니다. 설정 파일에서 취약한 부분을 찾아 패치하는 것입니다.
Ubuntu 환경에서 apt로 redis 패키지를 설치하였다면 /etc/redis/redis.conf경로에 설정파일이 있습니다.
0.0.0.0으로 바인딩 되어있는 라인을 찾아 삭제하고 bind 127.0.0.1 ::1 로 설정해 로컬 머신에서만 접근할 수 있도록 해야 합니다.
https://suzxc2468.tistory.com/178
혹은 내부 서비스 네트워크에서만 접속하게 하려면 접속하는 대상의 아이피를 명시해 추가해야 합니다.
Nginx Alias Path Traversal
Nginx에서 경로를 설정할 때 alias, root 두가지 방식이 있습니다.
alias는 요청한 경로를 지시한 다른 경로로 변경하는 역할을 하지만 root 는 해당 경로의 root 경로를 명시해주는 역할을 합니다.
alias는 http://server/i/top.gif 를 요청하면 /data/w3/images/top.gif 을
root는 /data/w3/images/images/top.gif를 가져옵니다. >> 한번더 images/
만약 사용자가 location을 지정할 때 /i/가 아닌 /i를 사용한다면 다음과 같은 상황이 발생합니다.
한 단계 상위 디렉토리의 파일을 가져올 수 있게 됩니다. 취약한 설정을 적용해 직접 서버에서 확인해보겠습니다.
syscall 트레이서인 strace를 이용해 nginx worker의 동작을 살펴보겠습니다.
/dreamhack/e.xxd를 요청했을 때 /tmp//e.xxd 를 읽어오는 것을 확인할 수 있고
/dreamhack../etc/passwd를 요청하면 /tmp/../etc/passwd를 읽어옵니다.
조치 방안
첫 번째는 alias 대신에 root directive를 사용할 것을 권장합니다. 취약한 설정은 아래의 방식으로 조치할 수 있습니다.
두 번째 방법은 location 마지막에 /를 붙이거나 alias 맨 뒤 /를 삭제해 조치하는 것입니다.
정상적인 요청은 openat이 성공하고 공격자의 요청은 ENOENT 에러와 함께 실패하는 것을 확인할 수 있습니다.
NGINX Proxy SSRF
Nginx에서 외부 이미지를 다운받아 화면에 보여주는 URL을 아래와 같이 구성하는 엔드포인트를 만드려고 합니다. StackOverflow 검색하면 아래 답변이 나옵니다.
Open-Proxy가 될 수 있다고 답변 작성자는 경고했지만 제대로 읽지 않고 그대로 사용할 경우 /image/ location에서 Server-Side-Request-Forgery(SSRF, SSRF 강의) 취약점이 발생해 nginx를 통해 내부 서비스 네트워크에 접근할 수 있습니다.
위와 같은 설정에서 http://dreamhack.io/image/http://127.0.0.1:1234 를 접속하게 되면 로컬 호스트에서 연결이 맺어지는 것을 확인할 수 있습니다.
조치 방안
SSRF 취약점으로 부터 안전해지기 위해서 설정 파일을 수정해야 합니다. 개발자가 미리 정의한 호스트에게만 요청할 수 있도록 변경합니다.
위 설정은 hostA와 hostB를 제외한 어떠한 호스트도 프록시 패스를 허용하지 않습니다.
Quiz
Injection
Misconfiguration
'보안공부 > 배운내용정리' 카테고리의 다른 글
dreamhack 04.Client-side Advanced (0) | 2021.05.21 |
---|---|
Flask (0) | 2021.05.18 |
dreamhack2-2 (0) | 2021.05.14 |
dreamhack 02 (0) | 2021.05.14 |
dreamhack01-2 *0514 (0) | 2021.05.14 |