이건 버그바운티는 아니지만 리액트에서의 XSS 공격에 대한 대응방안을 잘 적어놨다. XSS 공격은 버그바운티 뿐만 아니라 실무에서도 많이 나오는 공격이기 때문에 좋은 대응 방안은 알면 알수록 도움이 많이 된다. 생각보다 어렵진 않다. dangerouslySetInnerHTML이라는 속성 사용을 자제하면 리액트에선 간단하게 XSS 공격이 막힌다. 대신 아래와 같은 코드를 사용한다.
한국어로 된건 많이 없어서 생각보다 많이 어렵다. 그나마 해당 링크가 잘나와있는 편인데 좀 간단하게 말해서 동일한 HTTP 요청을 여러종류의 서버들이 각기 다른방식으로 처리하기 때문에 발생되는 취약점 중 하나다. Smugging bug 중에서도 CLTE 기법을 사용했는데 해당 공격기법은 Content-Length 헤더와 Transfer-Encoding 헤더를 동시에 전송하는 공격기법이다. Transfer-Encoding은 hop-by-hop 헤더로, 리소스 자체가 아닌 두 노드 사이에 메시지를 적용한다. 프론트엔드 서버에서는 Content-Length를, 백엔드 서버에서는 Transfer-Encoding을 각각 해석하는것을 이용하여 요청을 변조한다.
adb shell am start –n com.android.insecurebankv2/.ChangePassword 명령어를 실행하면 그림 1-1과 같이 패스워드 변경 액티비티가 실행된다. 하지만 adb 명령어로는 패스워드 변경 권한이 있는지 없는지에 대한 여부는 알 수 없다.
[그림 2-2] 노출된 액티비티 목록
드로저를 실행해 run app.activity.info –a com.android.insecurebankv2 명령어를 실행하면 현재 노출된 액티비티 정보 목록을 검색해 볼 수 있다. 액티비티에선exported속성이존재하는데true로선언이되있으면 [그림 1-2]와같이외부에서실행가능한상태로노출되며false로선언이되있으면외부에서실행할수없다. 맨 마지막 줄에 ChangePassword 액티비티가 있는데, 이미 노출되고 있음을 파악할 수 있다.
[그림 2-3] 드로저 명령어 실행
드로저 창에서 run app.activity.start--component com.android.insecurebankv2 com.android.insecurebankv2.ChangePassword 명령어를 입력하고 실행하면 그림 1-3과 같이 ChangePassword 액티비티가 실행된다.
[그림 2-4] 아이디 값 노출
드로저 창에서 run app.activity.start--component com.android.insecurebankv2 com.android.insecurebankv2.ChangePassword --extra string uname jack 명령어를 입력하면 [그림 2-4]와 같이 아이디 값이 들어가있는 상태로 액티비티가 실행된다.
기존의 비밀번호를 변경해본다. 패스워드는 대, 소문자, 특수문자가 섞여야 변경되기 때문에 최대한 어렵게 변경한다.
[그림 2-7] 로그인 성공
비밀번호를 변경하고 로그아웃 해서 변경된 패스워드로 다시 로그인한다. 변경된 패스워드로 로그인이 되면 [그림 2-7]과 같은 화면이 나온다.
3. 대응 방안
[그림 3-1] 코드 변경
Android Manifest.xml을확인한후 Android:exported=”true”를 false로 변경한다. exported는외부 application 에서 launch 가능 여부를 나타내는속성이다. 해당속성이 true면다른애플리케이션에서액티비티를실행할수있고 false인경우엔동일한애플리케이션에서만실행할수있거나같은사용자 ID를가진애플리케이션만실행이가능하다.
[그림 3-2] 권한 거부
run app.activity.start --component com.android.insecurebankv2 com.android.insecurebankv2.ChangePassword --extra string uname jack 명령어를드로저 창에입력하면권한이거부되었다는메시지가나오게된다. 권한이 거부되면바로 ChangePassword 창에접근을할수없게된다.
[그림 3-3] 권한 목록 확인
run.app.activity.info –a com.android.insecurebankv2 명령어를 드로저 창에 입력해권한목록들을확인해보면 ChangePassword는없는것으로확인된다. 이제 ChangeActivity에접근하려면 정당한절차를거쳐야실행할수있다.
웹 뷰는 앱을 개발할 때 웹 브라우저에서 보이는 화면을 표시하거나 웹 애플리케이션 등을 만들 때 자주 사용한다. 웹 뷰는 안드로이드 내부 모듈의 웹 킷 렌더링 엔진을 사용해 자바스크립트를 사용할 수 있다. 하지만 자바스크립트가 악의적인 목적으로 사용되면 XSS 공격이 가능하고 다른 사용자의 중요 정보를 탈취할 수 있는 취약점이 된다. 현재 인시큐어뱅크 앱에서도 XSS 공격이 가능하기 때문에 어떻게 취약점을 방어할 수 있는지에 대해 알아본다.
2. 취약점 진단
[그림 2-1] XSS 공격 스크립트
인시큐어뱅크 앱에 로그인 한 후 Transfer 버튼을 클릭한다. XSS 취약점이 발생하는 곳은 계좌번호를 보내는 칸이다. To Account 칸에 <script>alert(“Test”)</script>를 입력한 후 Amount를 지정하고 Transfer 버튼을 클릭해 정보를 ViewStatement에 전송한다.
[그림 2-2] XSS 공격 결과
Transfer에서 보낸 정보들을 확인하기 위해 ViewStatement를 확인하면 그림 4‑2와 같이 XSS 공격이 되었음을 확인할 수 있다. XSS 공격은 몇가지 종류가 있다. XSS 공격 종류는 Stored XSS 공격, Reflected XSS 공격이 있다. 두가지 공격은 성격이 조금씩 다른 공격인데, Stored XSS 공격은 공격자가 악성코드를 웹 서버에 저장하고 사용자가 악성스크립트를 심어 놓은 게시글을 실행하기를 기다리지만 Reflected XSS 공격은 URL주소에 악성스크립트를 삽입해 사용자를 유도해서 의도하지 않은 URL로 이동되어 피해를 준다. Reflected XSS 공격도 쇼핑몰에서 사용자가 원치 않은 가격 결제, 바이러스가 있는 사이트로 이동될 수 있는 피해가 발생할 수 있다. 일반 공격스크립트로 작성하면 사용자가 유심히 보면 금방 알아챌 수 있기 때문에 URL 인코딩 방법으로 작성하는 공격이 일반적이다. 모바일 진단에서도 XSS 공격은 웹과 거의 유사한 성격을 띤다.
[그림 2-3] sdcard 목록
adb shell 명령어를 입력해 단말기 내부에 접속한다. 경로를 외장 메모리인 sdcard로 경로를 바꾼다. ls -l 명령어를 입력해 파일 목록을 확인해보면 Statements_jack.html 파일이 있다. 여기에 Transfer에서 보낸 정보들이 저장되어 있다. Cat Sta* 명령어를 입력해 확인하면 XSS 공격을 하기위해 사용했던 자바스크립트를 확인할 수 있다. 이제부터 XSS 공격을 막기 위해 시큐어 코딩을 할 것이다. 만약 시큐어코딩이 성공한다면 XSS 공격은 더 이상 할 수 없고 Statements_jack.html 파일에도 평문 스크립트가 아닌 특수 문자로 처리된 스크립트를 볼 수 있다.
3. 대응 방안
[그림 3-1] 시큐어 코딩 – 1
XSS 취약점 공격을 방어하기 위해 시큐어 코딩을 한다. DoTransfer.java 파일에서 getText() 함수가 있는 곳에 XSS 공격에서 주로 쓰이는 문자열들을 필터링 하는 코드를 추가한다. 주로 >,<,\\ 등의 문자열이 있다. 코드를 추가한 후 아래에서 출력해주는 변수 값도 바꿔줘야 코드가 정상적으로 작동한다.
[그림 3-2] 시큐어코딩 – 2
DoTransfer.java 파일에서 아래로 내려와 JSONObject 함수, makeText() 함수를 찾아 그림 1-4와 동일한 XSS 공격에서 주로 쓰이는 문자열들을 필터링하는 코드를 추가한다. 이 때, 출력되는 함수값도 같이 변환해야 정상적인 값이 출력된다.
[그림 3-3] XSS 공격
시큐어 코딩을 한 파일을 다시 실행시켜 녹스플레이어에 인시큐어뱅크 앱을 설치한다. 인시큐어뱅크 앱이 설치된 후 로그인해서 DoTransfer에 XSS 공격 스크립트를 넣고 Transfer 버튼을 클릭한다.
[그림 3-4] 공격 결과
ViewStatement를 실행해 결과를 보면 XSS 공격 문자열이 필터링 되서 공격이 불가능하다. 공격성 문자열이 필터링되서 참조 문자로 인코딩됐기때문이다. 주로 사용되는 XSS 공격 문자는 [표 1-1]과 같다.
ASCll문자
참조문자
&
&
<
<
>
>
(
(
*
"
‘

)
)
/
'
[표 1-1] 가장 많이 쓰이는 위험 문자
[그림 3-5] sdcard
adb shell 명령어를 입력해 단말기 내부에 접속한다. 경로를 외장 메모리인 sdcard로 경로를 바꾼다. ls -l 명령어를 입력해 파일 목록을 확인해보면 Statements_jack.html 파일이 있다. 여기에 Transfer에서 보낸 정보들이 저장되어 있다. Cat Sta* 명령어를 입력해 확인하면 XSS 공격을 하기위해 사용했던 자바스크립트를 확인할 수 있다. 단말기 내부의 메모리 확인 결과 XSS 공격 문자열이 필터링 됨을볼수 있다. XSS 공격은 모바일 뿐만 아니라 웹에서도 같은 방법으로 차단이가능하다.
안드로이드 애플리케이션은 실행이 되는 도중에 중요한 정보들을 저장할 때가 많다. 하지만 중요한 정보들을 저장할 때 제대로 암호화 되지 않은 상태에서 저장되게 된다면 제 3자에게 중요 정보, 민감한 정보 등이 유출될 수 있다. 암호화는 됐지만 디코딩 도구들로 해독이 되어도 문제가 된다. 취약점 점검에 앞서 암호화 할 때 크게 2가지로 나눌 수 있는 대칭키와 공개키가있다.
대칭키
공개키
키의 관계
암호화 키 = 복호화 키
암호화 키 x 복호화 키
전달 방식
직접 전달
비밀경로를 통한 전달
비용
싸다
비싸다.
암호화 키
비밀
공개
복호화 키
비밀
비밀
속도
빠르다
느리다
단점
키 교환 원리가 없다
중간자 공격에 취약
[표 1-1] 대칭키와 공개키의 비교
대칭키는 암호화 키와 복호화 키가 같다. 즉, 하나의 키로 암호화와 복호화를 한다. 이런이유로키를 보내지 않으면 수신자는 수신한 암호문을 복호화 할 수 없게 된다. 그래서 대칭키의 최대 단점이 키 배송 문제가 있다. 또한 하나의 키로만 사용해서 키가 노출될 경우 모든 암호문을 복호화 할 수 없다는 단점도 존재한다. 반면 공개키는 암호화 키와 복호화 키가 서로 다르기 때문에 키 배송문제가 발생하지 않는다. 주로 비밀 경로를 통해서 키가 전달되게 된다. 하지만 공개키도 단점은 있다. 중간자 공격에 매우 취약하며 비용이 비싸다.
2. 취약점 진단
[그림 1-1] 인시큐어뱅크 로그인 페이지
인시큐어뱅크 앱을 실행하면 로그인 화면이 보인다. 여기서 Autofill Credentials를 클릭하면 자동으로 아이디, 패스워드를 생성한다. 이는 아이디, 패스워드가 앱 내부 데이터에 저장되어 있을 가능성이 크다. 실제 금융권 앱을 사용하다 보면 [그림 1-1]과 같이 자동으로 아이디, 패스워드를 생성해주는 기능이 있다.
[그림1-2] adb shell
인시큐어뱅크 앱을 실행한채로 cmd 창을 실행해 adb shell 명령어를 입력해 가상 디바이스 내부로 접속한다.
[그림 1-3] 인시큐어뱅크 데이터 저장
adb shell 명령어를 실행한 후 cd /data/data/com.android.insecurebankv2를 입력해 인시큐어뱅크 앱의 데이터가 저장되는 장소를 확인한다.
[그림 1-4] 데이터 저장 장소
cd com.android.insecurebankv2를 입력해 데이터가 저장되는 장소로 이동 후 ls -l 명령어를 입력해 어떤 정보가 저장되어 있는지 확인한다.
[그림 1-5] adb pull 명령어
C 드라이브에 tmp 폴더를 하나 생성하고 tmp 폴더로 경로를 이동해 adb pull /data/data/com.android.insecurebankv2 .\를 입력해 인시큐어뱅크 앱에 저장된 데이터들을 로컬로 가져온다. 성공적으로 명령어가 실행되면 7 file pulled. 0 files skipped 라는 메시지가 나온다. 각각의 데이터 경로에 저장 되어 있는 파일 중에 Insecurebankv2_preferences.xml는 사용자 아이디를 포함한 EncryptedUsername을 포함하고 비밀번호를 포함한 superSecurePassword 문자열 변수가 있다. com.android.insecurebankv2_preferences.xml는 서버의 아이피와 포트 정보가 포함되어 있다. 또한 Shared_Preferences는 초기 설정값과 자동로그인 정보들이 애플리케이션 저장 공간 안에 파일 형태로 저장된다.
[그림 1-6] 데이터베이스
로컬로 가져온 데이터들 중 데이터베이스 폴더에 있는 mydb 파일을 sqliteBrowser 도구를 활용해 연다. 파일이 열리고 Browse Data 탭의 테이블에서 names를 선택하면 인시큐어뱅크 앱에서 로그인했던 아이디 정보가 나온다. 하지만 아이디 정보만 나오고 비밀번호는 나오지 않아 원하는 정보를 얻을 수 없다.
[그림 1-7] 계정 정보
com.android.insecurebankv2_preferences.xml 파일을 인터넷 익스플로러로 연다. 인터넷 익스플로러로 열면 파일 내용이 브라우저에 나오는데 인시큐어뱅크 앱에서 로그인했던 정보들이 암호화가 되어 있는 상태로 있다. Base64 Decorder 도구를 활용해 아이디, 패스워드를해독한다.
[그림 1-8] 아이디 암호 해독
암호 해독을 위해 Fiddler 도구를 활용한다. Fiddler를 실행해 TextWizard 탭을 클릭하면 암호를 해독할 수 있는 텍스트가 출력된다. From Base64로 옵션을 지정하고 그림 3‑7에서 Encrypted Username의 내용을 입력해 복호화한다. 복호화가 되면 jack이라는 메시지가 출력된다. 이는 인시큐어뱅크 앱에서 로그인 했던 아이디 값임을 알 수 있다.
[그림 1-9] 비밀번호 암호 해독
[그림 3‑8]과 같은 방법으로 이번엔 superSecurePassword의 내용을 입력해 복호화한다. [그림 3‑8]과 달리 해독 내용이 알 수 없는 문자들로 출력된다. Base 64 옵션 외에 다른 옵션을 지정해도 결과는 같다. 패스워드는 확실하게 암호화가 되어 있다는 사실을 확인할 수 있다.
3. 대응방안
인시큐어뱅크 앱에서 데이터를 저장하는 방식이 가장 큰 문제점은Base 64 방식으로 암호화를 한것이다. 하지만 Base 64 방식은 특정 알고리즘에 의해 치환되는 형태기 때문에 암호 해독 도구를 이용하면 쉽게 디코딩 될 수 있다. 아이디 부분도 패스워드와 같이 AES 형태를 적용해야 한다.
분류
미국
일본
국내
대칭키 알고리즘
AES-128/192/256
AES-128/192/256 3TDEA
SEED, HIGHT ARIA-128/192/256
공개키 알고리즘
RSA
RSAES-PKCS1, RSAES-OAEP
RSAES-OAEP
일방향 알고리즘
SHA-224/256/384/512
SHA-256/384/512
SHA-224/256/384/512
[표 1-2] 해외 및 국내 암호화 방식
[표 3‑2]는 KISA에서 제공해주는 암호화 방식 가이드 라인이다. 인시큐어뱅크 앱에서 사용한 AES-256비트는 강력한 암호 방식에 속한다. 하지만 암호키 값을 동일하게 유지하면 다른게 보안이 잘 되어 있어도 쉽게 뚫릴 수 있는 소지가 있기 때문에 늘 암호키 값을 바꿔 줘야 한다. 그래서난수라는 기술을 적용한다. 암호키는 탈취되서 해독되면 2차 피해가 발생할 가능성이 높기 때문에 가장 강력한 암호 기술을 적용해야 한다.
[그림 1-10] 수정된 LoginActivity.java
[그림 3‑10]은 암호화와 관련이 있는 LoginActivity.java 코드이다. 상단 부분 코드를 보면 username을 Base64 방식으로 암호화 한 것을 볼 수 있다. 이 부분을 주석 처리 하고 Username_Text 위에 AES 암호화 방식 코드를 추가한다. 이미 Password_Text는 AES 암호화 코드가 적용 되어 있기 때문에 따로 추가할 코드는 없다. 결론적으로 Username_Text에 아이디, 비밀번호가 입력 되서 로그인 되면 String decryptedUsername = crypt.aesDecryptedString(username); 코드에 의해 아이디가 AES 암호화방식으로 앱 내부 데이터에 저장된다.
[그림 1-11] 수정된 DoLogin.java
LoginActivity.java 코드를 수정한 후 DoLogin.java 파일의 코드도 수정한다. 현재 DoLogin.java 파일에서도 username는 Base64 암호화 방식을 사용하기 때문에 해당 코드 부분을 주석 처리 하고 String encryptedUsername = crypt.aesEncryptedString(rememberme_username);을 추가해 AES 암호화 방식으로 변경한다. MYPREFS 변수 윗부분의 데이터 저장 파일 이름이 mySharedPreferences로 정의되어 있다. 파일 생성 후 사용자의 아이디, 패스워드를 저장해 CryptoClass 함수에 의해 암호화 된다. 코드를 추가한 후 다시 디버깅해 인시큐어뱅크 앱을 가상 디바이스에 설치한다.
[그림 1-12] adb pull 명령어
인시큐어뱅크 앱이 가상 디바이스에 설치되면 다시 로그인 한 후 만들어 둔 tmp 폴더 경로를 지정하고 adb pull /data/data/com.android.insecurebankv2 .\를 입력해 인시큐어뱅크 앱에 저장된 데이터들을 로컬로 가져온다.
[그림 1-13] 계정 정보
com.android.insecurebankv2_preferences.xml 파일을 인터넷 익스플로러로 연다. 익스플로로러가 실행되면 인시큐어뱅크 앱에서 로그인했던 정보들이 암호화가 되어 있는 상태로 있다. 아이디 내용을 보면 [그림 1-7]과는 내용이 조금 달라졌다는 걸 추측할 수 있다. 암호를 해독하기 위해 Fiddler 도구를 실행한다.
[그림 1-14] 아이디 암호 해독
TextWizard 탭을 실행해 암호 해독 도구를 실행하고 From Base64 옵션으로 바꾼 후 Encrypted Username의 내용을 입력해 복호화한다. [그림 1-7]과는 달리 아이디 값이 평문으로 출력되지 않고 여전히 암호화 되서 출력됨을 알 수 있다. 다른 옵션으로 바꿔서 실행해도 결과값은 같다.
[그림 1-15] 패스워드 암호 해독
[그림 1-14]와 같은 방법으로 이번엔 superSecurePassword의 내용을 입력해 복호화한다. 패스워드는 여전히 해독 내용이 알 수 없는 문자들로 출력된다. Base 64 옵션 외에 다른 옵션을 지정해도 결과는 똑같다. 이로써 아이디, 패스워드 모두 안전하게 암호화 조치가 완료되었다.