설치방법
안녕하세요! 오늘은 앱해킹 기법 공부를 위하여 Anroid 용 Diva 설치를 진행하여 보겠습니다! 실습을 진행하면서 디컴파일러 프로그램인 JADX 는 계속 사용할테니 만약 설치를 안하셨다면 밑에 링크로 들어가셔서 설치를 진행하고 오세요!
https://github.com/skylot/jadx/releases
DIVA app 이란? : 모바일 앱 보안을 공부하며 실습해볼수 있도록 만든 어플을 의미합니다! 총 13가지의 문제로 구성되어있고 각 문제마다의 취약한 부분을 찾는방식이라 앱 해킹 기초를 공부할때 매우좋습니다!
이 앱에서 공부할 수 있는 항목은 크게보면 총 5가지입니다.
Diva app의 13가지 문제 | 5가지 분류 |
1 . 취약한 로깅 2. 하드코딩 이슈 3. 취약한 데이터 저장 4. 입력값 검증 이슈 5. 접근 제어 이슈 |
우선 오늘은 설치법을 설명하고 다음 포스팅부터 차례대로 취약점에 대하여 소개해드리겠습니다!
[설치방법]
1 . DIVA app 다운로드
아래 링크로 들어가셔서 DivaApplication.apk 라고 적힌 부분을 클릭하셔서 다운로드 하세요!
https://github.com/0xArab/diva-apk-file
GitHub - 0xArab/diva-apk-file: DIVA (Damn insecure and vulnerable App) is an App intentionally designed to be insecure
DIVA (Damn insecure and vulnerable App) is an App intentionally designed to be insecure - 0xArab/diva-apk-file
github.com
2 . Nox 앱플레이어에 DIVA app을 업로드
다운로드 된 것을 확인후 이제 NOX 앱플레이어에 드래그 앤 드롭으로 설치해줍니다.
3 . DIVA app을 실행!
이제 앱을 실행시켜보시면 해당문제들을 확인하실 수 있습니다!
취약한 로깅
'취약한 로깅' 이란?
> 모바일 앱 개발을 하면서 변수값이 잘들어가는지 , 어디서 오류가 나는지 확인하기위하여 Log.d 나 Log.e 를 자주 활용하게 됩니다. 하지만 개발 규모가 커지다 보면 이러한 디버깅했던 부분들을 미처 지우지 못하고 배포해버리는 경우가 종종 있습니다. 이럴경우 피해자의 시스템으로 접근시 앱의 로그를 살펴보면 '비밀번호 평문','아이디' 같은 중요정보가 평문으로 노출되어 있는경우가 있는데 이러한 취약점을 취약한 로깅 이라고 일컫습니다.
[실습]
1. 우선 모바일 Diva 어플을 실행시켜 주신 후 INSECURE LOGGIN으로 들어갑니다.
2. 문제 설명을 보니 '무엇이 로그되는지 확인하고 , 어디서 어떻게 취약한 코드가 있는지 확인하라' 입니다.
일단 어떤 동작을 하는지 입력란에다가 15880924 라고 적어봅시다!
3. 에러가 뜨는군요... 그러면 동작도 해보았으니 디컴파일러 Jadx를 통해 디컴파일링을 해봅시다.
4. Jadx로 저희가 다운로드 받은 Diva 파일을 열어줍니다.
5.보시면 LogActivity 파일의 코드에 저희가 15880924 를 입력하였을때 떳던 toast메세지에 대한 코드가 있습니다. 근데 저희가 입력한 변수는 cctxt 라는 EditText형 변수에 들어가는것으로 보이는데 , 이 값을 Log.e 를 통해서 diva-log 라는이름의 에러로, 값을 평문으로 로그에 저장하는것을 확인하실 수 있습니다!
↓ '취약한 로깅' 발생부분
(diva-log 라는 key값의 에러를 로그창에 기록하고있다)
6. 이 앱의 로그확인을 위하여 cmd 창을 켠후 다음의 명령어를 입력합니다. 보시면 저희가 입력한 credit card 비밀번호인 15880924 가 평문으로 로그기록에 저장되어 있는것을 확인하실 수 있습니다.
nox_adb shell "logcat | grep diva-log"
[왜 위험한가?]
>이를통해 시스템의 로그에 접근하는 것만으로도 비밀번호 정보를 탈취할 수 있고, 이 정보를 가지고 여러가지 비밀번호르 조합하여 브루트포스 공격을 통해 다른 앱이나 사이트에 가입된 계정을 탈취할 가능성이 존재하기 때문입니다.
[대응방안]
> 이는 디버깅 과정을 개발끝난이 이후에 지워야 하는데 그러지 않아서 발생한 문제점입니다. 또한 error 자체를 출력시키는건 괜찮은데 사용자가 입력한 값이 아무런 암호화도 없이 평문으로 저장된것이 문제점입니다. 따라서 로그에다가는 입력값을 저장 안하는것이 가장 좋은 해결책이고 , 만약 굳이 굳이 굳이! 꼭 로그로 입력값을 저장해야 겠다 한다면 다음과 같이 수정할 수 있습니다. 저장될 credit card number를 hash 암호화 하여 로그에 저장하는 것 입니다.
[만약 암호를 로그에 저장시 암호화 되어 저장되는 코드]
/* 원본 import 코드와 동일 */
import java.security.MessageDigest; //추가된 import
import java.security.NoSuchAlgorithmException; //추가된 import
public class LogActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_log);
}
public void checkout(View view) {
EditText cctxt = (EditText) findViewById(R.id.ccText);
try {
// cctxt의 평문값을 바이트 배열로 변환
byte[] data = cctxt.getText().toString().getBytes();
// SHA-256 해시 처리
MessageDigest hash = MessageDigest.getInstance("SHA-256");
byte[] value = hash.digest(data); // 해시 계산
// 해시값을 16진수 문자열로 변환
StringBuilder hexString = new StringBuilder();
for (byte b : value) {
hexString.append(String.format("%02x", b)); // 16진수로 포맷
}
// 해시값을 로그에 기록
Log.e("diva-log", "Error while processing transaction with credit card: " + hexString.toString());
Toast.makeText(this, "An error occurred. Please try again later", Toast.LENGTH_SHORT).show();
} catch (NoSuchAlgorithmException e) {
// 해시 알고리즘이 존재하지 않는 경우 처리
Log.e("diva-log", "Hashing algorithm not found", e);
Toast.makeText(this, "An error occurred. Please try again later", Toast.LENGTH_SHORT).show();
}
}
private void processCC(String ccstr) {
// 카드 처리 로직
throw new RuntimeException();
}
}
하드코딩 이슈
Hardcoding Issue 란?
> 소스코드 내에 중요한 정보를 직접 입력해놓는 보안사고를 말합니다. 예를들어 Admin 페이지로 들어가려면 PIN번호를 작성해야 하는데 인증절차 과정에서 if( user의 입력값 == '591354') 이런식으로 코드에 PIN번호를 직접적으로 노출시키는 등 중요정보를 코드에 담는 경우가 있습니다. 앱의 코드같은 경우 Jadx 로 디컴파일이 가능하기 때문에 중요정보가 공격자에게 직접적으로 노출되기에 반드시 개발과정에서 조심해야합니다.
[실습]
1. Hardcoding Issues - part 1
1. Diva 앱에서 두번째 문제인 Hardcoding Issues - part 1 을 클릭해줍니다.
2. 문제를 보니 '어디가 하드코딩 되어있는지 찾아라' 라고 써있습니다.
3. 비밀번호에 159159 를 넣어봤지만 접근제한 문구가 뜨네요.. 그러면 한번 jadx로 디컴파일 하여 코드를 살펴보겠습니다.
4. 디컴파일된 소스코드중에서 HardcodeActivity 라는 액티비티로 접근시 저희가 방금 했던 인증과정에 관한 코드가 적혀있습니다. 근데 보시면 if 문안에 저희가 입력한 값과 "vendorsecretkey" 라는 문구가 같으면 접근을 성공시키는 문구가 뜬다고 되어있습니다. 이 문구를 다시 Diva 앱으로 들어가서 넣어주겠습니다.
5. "vendorsecretkey" 라는 글자를 적자 접근이 허용되었다는 메세지가 뜨는것을 확인하실 수 있습니다.
> 이런식으로 개발과정중 인증절차의 대한 정보가 직접적으로 노출된 경우를 하드코딩되었다 라고 말할수 있습니다.
2. Hardcoding Issues - part 2
1. 12번째 문제인 Hardcoding Issues - part 2 에 들어갑니다
2.음 ...문제 구성은 일단 part1 과 동일한 것 같습니다. jadx로 디컴파일링 하여 소스코드를 살펴봅시다
3. 음... 일단 Hardcode2Activity 소스코드에 들어와서 코드를 살피던중 인증절차 부분을 발견했습니다. 코드를 보시면 위에서 부터 DivaJni 라는 셩식의 djni라는 이름의 객체를 만들어 준후 , djni에 새로운 DivaJni 인스턴트를 부여해주었습니다. 그 이후 if 문에서 djni클래스에 있는 access라는 함수를 통해 저희가 입력한 값과 비교를 하여 인증절차를 진행하는 듯 보였습니다. "그래서 도대체 DivaJni 라는 클래스가 뭐지?? access 함수는 또 뭐고?" 라는 의문이 들던중 옆에 소스코드를 살펴보니 Divajni 라는 클래스가 보여서 들어가보았습니다.
4.들어와 보니 여기선 divajni 라는 이름의 라이브러리를 가져와서 사용하는 듯 보였습니다. 아마 이 라이브러리에 access함수에 관한 코드가 적혀있을듯 합니다.
5. 리소스 > lib 로 가셔서 폴더를 열어보면 libdivajni.so 라는 이름의 바이너리 파일들이 있습니다. 아마 이 파일에 저희가 불러오는 divajni 라이브러리와 관련이 있는 내용들이 정의가 되어있는 것 같습니다. 하지만 .so 파일이나 .dll 파일은 바이너리 파일이기 때문에 jadx로는 디컴파일이 불가능합니다. 이를 위해서 디스어셈블러 툴인 Ghidra를 활용해줄것입니다!!
Ghidra 가 뭔데요?...
만약 Ghidra 가 무엇인지 모르겠고 설치가 안되셨다면 제 포스팅중 관련 포스팅이 있으니 설치를 진행하고 다시 와주세요!
[Reversing] 디스어셈블러 툴 GHIDRA 설치방법
안녕하세요! 이번시간은 무료로 사용할 수 있는 디스어셈블러 툴인 GHIDRA 설치방법에 대한 포스팅 입니다. 모바일 DIVA 문제를 살펴보다가 .so 확장자 파일을 열어봐야 했는데 , .so 파일은 바이너
jamesbexter.tistory.com
6. Ghidra 를 통해 libdivajni.so 파일을 읽으려면 일단 nox에서 local 컴퓨터로 libdivajni.so 파일을 옮겨줘야 합니다. 이를 위해서 cmd 창에서 NOX안에 파일을 복사해서 내 컴퓨터로 가져오는 명령어를 실행시켜줄 것입니다.
NOX에 있는 libdivajni.so 파일의 위치는 /data/user/0/jakhar.aseem.diva/lib 에 위치합니다! 여기에 위치한 libdivajni.so 파일을 내 컴퓨터로 가져와야 합니다!
내 컴퓨터로 파일을 복사하기 위해서 cmd창에 다음과 같은 명령어를 입력해주세요.
nox_adb pull /data/user/0/jakhar.aseem.diva/lib/libdivajni.so
※ 내 컴퓨터로 저장될 때 파일이 저장되는 위치는 현재 위치한 디렉토리에 저장됩니다!
7. 무사히 파일이 복사된것을 확인한 후 Ghidra를 통해 바이너리 파일을 열어주세요.
8. 왼쪽 하단에 Symbol Tree 라는 부분에 access 라는 문구를 검색하면 두개의 문구가 검색이 되는데 그중에 Functions 라고 되어있는 부분을 클릭해주시면 저희가 분석해야할 access 라는 함수가 정의되어 있는 곳으로 이동하게됩니다.
9. 인증절차 부분일테니 분명 비교하는 연산자가 쓰였을 가능성이 높습니다. 그런 생각을 가지고 보던중 연산자 중에 CMPSB 라는 문자열을 비교할때 쓰는 연산자가 보입니다. 간단히 알아보자면 CMPSB 라는 연산자를 써서 ESI(비교될 문자열) 과 EDI(우리가 입력한 값) 을 비교하는 부분인 것 같습니다.
10. 코드의 흐름을 살펴보기 위해 맨 우측 디컴파일창에 써있는 코드를 살펴봅시다.
1. 우선 access 함수에 대한 정의입니다.
2.저희가 찾던 비밀코드는 olsdfgad;lh 인것 같습니다. 이 문자열을 pcVar3 라는 변수에 넣어줍니다.
3. 이후 while문을 돌면서 저희가 입력한 값(pcvAR1) 과 비밀코드를 한글자씩 비교합니다.
4. 만약 비교하다 문자열이 서로 틀리면 uVar4값이 false 가 되어 반환되고 , 문자열이 전부 일치하면 uVar4가 true가 되는것 같습니다.
대강 이런 코드의 흐름을 가지고 진행되는 것 같습니다. 저희가 원하는 비밀코드는 얻었으니 이제 이 값을 NOX로 돌아가 삽입해보겠습니다!
11. Ghidra를 통해 얻은 코드를 넣자 접근에 성공했다는 문구가 뜨는것을 확인하실 수 있습니다!!!
3. 대응방안
part 1 &2 전부다 코드에 직접적으로 인증절차를 위한 정보가 평문으로 작성되는 것이 문제입니다! 이 문제점에 대응하는 방법은 다음과 같습니다.
> 우선 사용자가 디컴파일링 할 수 있는 APK파일엔 인증에 대한 정보를 작성하지 않아야 합니다. 인증절차를 위한 비밀번호는 암호화하여 DB에 저장합니다. 이후 인증절차를 거칠때 서버는 DB에서 가져온 값과 사용자의 입력값을 비교하는 방식으로 진행하면 됩니다.
안전하지 않은 데이터 저장
Insecure Data Storage 란?
> 개인정보같이 민감한 정보를 편의성을 위하여 로컬저장소에 저장해놓을 때 데이터를 평문으로 저장해버리면 공격자에 의하여 저장소가 노출될 경우 사용자의 아이디와 패스워드가 그대로 노출될 수 있는 취약점을 의미합니다. 예를들어 자동로그인을 위하여 앱의 내부저장소에 아이디와 비밀번호 정보를 저장하는 경우 비밀번호를 평문 그대로 저장해버리면 앱 내부 저장소에 접근하는 것만으로도 사용자의 계정정보가 탈취될 수 있습니다.
[실습]
1. Insecure Data Storage - Part 1
1. 우선 Nox 앱플레이어에서 DIVA 앱을 실행 후 Insecure Data Storage - Part 1 을 클릭해주세요.
2. 어디서/어떻게 인증정보가 저장되는지, 코드상 취약한 부분은 어디인지 를 찾는 문제같습니다
3. 아이디: james , 비밀번호 : admin159 라고 하고 SAVE 버튼을 눌러보겠습니다.
4. 인증정보가 저장되었다고 뜨는군요, 그러면 디컴파일링 하여 이 정보가 어디에 저장됐는지 확인해보겠습니다.
5.Jadx 를 사용하여 디컴파일링 한 다음 소스코드를 보니깐 SharedPreferences를 사용하여 앱의 내부저장소에 user & password를 평문으로 저장하는것 같습니다. 이 저장된 내용을 찾아서 열어본다면 저희가 저장한 로그인 정보를 얻을 수 있습니다!
Tip. getDefaultSharedPreferences(this) 함수를 쓰면 Preference 파일의 이름이 "jakhar.aseem.diva_preferences.xml" 라는 이름으로 자동 설정됩니다!
6. nox_adb shell 에 접근후 , /data/user/0/jakhar.aseem.diva/shared_prefs 경로에 접근하시면 저장된 preferences.xml 파일을 확인하실 수 있습니다!
7. cat jakhar.aseem.preferences.xml 라고 명령어를 입력하여 xml 파일 내용을 확인하시면 방금 저장했던 로그인 정보를 평문으로 확인하실 수 있습니다!
2. Insecure Data Storage - Part 2
1. Part2 문제 형식을 보아하니 Part 1 과 동작방식은 크게 차이 없어보입니다.
2. 이번엔 아이디 : pacmax & 비밀번호 : admin159 라고 작성후 저장해주겠습니다.
3. InsecureDataStorage2Activity 로 들어와보시면 SQLite를 사용하여 로그인 정보를 저장하는 것을 확인하실 수 있습니다. 앱 내부저장소에 저장될 DB이름이 ids2 라고 설정한 후 그 안에다가 로그인 정보를 넣는것 같습니다. 또한 쿼리문중 Insert 구문을 사용하여 myuser 라는 이름의 테이블에 아이디&패스워드가 아무런 암호화없이 평문저장이 되는것을 확인하실 수 있습니다!
4. 저장된 DB의 경로는 "/data/user/0/jakhar.aseem.diva/databases" 입니다. 여기엔 앱 내부저장소에 저장된 DB들이 모여있는데 저희가 원하는 DB인 ids2 의 내용을 살펴보겠습니다.
5. 다음과 같은 명령어로 DB 내용을 확인하실 수 있습니다. ids2 의 DB내용을 확인해보시면 저희가 저장했던 값인 pacmax,admin159가 저장되어있는것을 확인하실수 있습니다! (그위에 kevinadmin159는 제가 따로 실습하면서 저장했던 값인것 같습니다..)
> sqlite3 ids2 (ids2 라는 DB를 sqlite3 를 이용하여 열어본다)
> .table (현재 DB의 테이블 명을 확인한다)
> select * from myuser; (myuser 라는 테이블의 내용을 전부 읽어온다.)
3. Insecure Data Storage - Part 3
1. Part3 의 문제 형식도 이전과 같은것 같습니다. 이번엔 아이디:taegookki&비밀번호:admin123 이라고 하고 저장을 해주겠습니다.
2. 디컴파일링 하여 InsecureDataStorage3Acitivity 의 소스코드를 확인해보니 이번 문제는 앱의 내부저장소에 파일형식으로 저장하는것 같습니다. 그러면 코드에서 이해가 필요한 부분을 차례대로 설명해보겠습니다.
1. getApplicationInfo().dataDir 은 파일저장 위치를 아래와 같은 경로로 자동설정됩니다. |
2. uinfo 로 시작하여 tmp로 끝나는 이름의 파일의 객체를 생성한다. |
3. 지정한 파일에 적었던 로그인정보를 평문으로 저장합니다. |
3. 이후 /data/user/0/jakhar.aseem.diva 경로로 들어가면 uinfo로 시작하고 tmp 로 끝나는 파일이 존재합니다.
4. 이후 cat info1431452964tmp 라는 명령어로 내용을 확인해보면 저희가 적었던 로그인정보가 평문으로 저장된 것을 확인하실 수 있습니다!
4. Insecure Data Storage - Part 4
1. 아이디 : Gomdol & 비밀번호 : admin159 를 입력후 저장해주겠습니다.
2. 그러면 파일 에러가 뜰것입니다... 무슨일인지 한번 디컴파일하여 소스코드를 살펴봐야겠습니다.
3. InsecureDataStorage4Activity 코드를 살펴보니 로그인 정보를 파일에 저장합니다. 근데 파일을 저장할 때 getExternalStorageDirectory() 함수를 사용하여 외부저장소에 접근한다는 특징이 있습니다.. 외부저장소에 접근하려면 따로 앱의 권한에서 저장권한을 ON 해주어야 합니다.
4. NOX 화면으로 돌아가서 Diva 아이콘을 꾹누르면 앱 정보 라는 항목이 뜨게되는데 클릭합니다.
5. 권한 메뉴로 접근하셔서 앱의 저장 권한을 부여해주세요.
권한 메뉴로 접근 | 저장 권한을 ON 하기 |
6. 이후 다시 저장해보시면 이번엔 무사히 저장되는 것을 확인하실 수 있습니다!
7. 자 그럼 저장도 했으니 소스코드를 다시 살펴보죠! .... 이번 문제는 전반적으로 Part3 문제와 매우 유사하지만 하나 다른점이 있습니다. 이번엔 외부저장소의 절대경로를 파일의 저장경로로 사용한다는 점입니다!
※외부저장소의 절대경로는 : /storage/emulated/0 입니다!
즉 밑에 코드대로면 파일의 저장경로는 /storage/emulated/0/.uinfo.txt 가 됩니다!
8. 실제로 해당 경로에 접근하게 되면 .uinfo.txt 라는 파일이 존재합니다. 이때 ls -al 이라는 명령어로 숨겨진 파일까지 전부 보게 해주어야 txt 파일이 표시되게 됩니다. 이후 cat 명령어로 txt 내용을 확인하시면 저희가 입력한 로그인 정보가 평문으로 저장되어 있는것을 확인하실 수 있습니다.
[대응방안]
이 문제는 민감한 정보를 평문 그대로 저장하는것이 원인이였습니다. 대응방안은 다음과 같습니다.
- 패스워드와 같은 민감한 정보는 앱이 삭제되면 같이삭제될 수 있도록 내부저장소에 저장
- 저장할 때 반드시 비밀번호와 같은 민감한 정보는 반드시 암호화 하여 저장(암호화는 Hash 암호화 추천)
입력값 검증 미흡
Input Validation Issues 란?
> 사용자의 입력값을 받는 과정에서 특별한 필터링이나 제한을 두지않아 예기치 못한 동작을 일으키는 경우를 말합니다. 예를들어 SQL Injection 인 경우 입력값에 대한 필터링이 충분히 되어있질 않아서 DB에 접근할 수 있게되는데 이런 경우도 Input Validation Issues 이라고 할 수 있습니다!
[실습]
1. Input Validation Issues - Part 1
1. 우선 INPUT VALIDATION ISSUES -PART 1 문제를 클릭하여 들어갑니다.
2. user name을 검색할 수 있는 페이지인것 같습니다. 문제에선 "user name 을 모르는 상태에서 모든 user data 에 접근해라. 3개의 고정된 user data 가 있는데 검색창을 이용해서 3명 모두의 정보를 얻어내라" 인것같습니다. 일단 당장은 뭘 검색해야 할 지 모르겠으니, 이 페이지가 어떻게 동작하는지 알아보기 위해 Jadx로 디컴파일링 하여 코드를 확인해봅시다.
3. 디컴파일링 된 소스코드중 SQLInejectionActivity 라는 액티비티에 들어가보면 SQLite를 이용하여 유저 데이터를 만드는것을 보실수있습니다. 또한 srchtxt 라는 변수에 저희가 입력한 값이 들어가게 되고 , 이후 별 다른 필터링없이 저희가 입력한 값을 SQL query 문에 포함하여 Select 문을 실행시키는것을 보실 수 있습니다. (만약 사진이 깨져서 잘 안보인다 하시면 사진을 클릭하시면 깨끗한 이미지로 확인가능합니다!)
4. 그러면 내용을 토대로 SQL Injection 구문을 삽입하여 Search 버튼을 눌러보겠습니다.
5. 앱 내부 저장소 DB에 저장되어 있던 USER 정보가 전부 뜨는것을 확인하실 수 있습니다!!
[대응방안]
1. Prepared Statement 사용
> Prepared Statement를 사용하면 쿼리문을 미리 컴파일 해놓고 사용자의 입력값은 나중에 따로 문자열로 처리하여 쿼리를 실행하기 때문에 ' " or and 등 사용자의 입력값이 SQL 쿼리문에 영향을 끼칠 수 없게 됩니다.
2. 블랙리스트 필터링 방식
> Prepared Statement 를 쓴다하여도 order by 절이나 컬럼명,테이블명을 적는 부분에는 여전히 사용자의 입력값이 쿼리문에 영향을 끼칠수 있기때문에 다음과 같은 단어들은 필터링 하시는것이 좋습니다.
특수문자 | 특정구문 |
" , ' , % , -- , # | select , update , delete , union , information_schema |
2. Input Validation Issues - Part 2
1. 두번째 문제는 Web View 를 이용하여 민감한 정보에 접근하라는게 목표인것 같네요!
2. 일단 동작체크를 위해 입력값에 naver에 주소를 적자 네이버 페이지가 nox 에 뜨는것을 확인하였습니다. 일단 어떤 동작을 하는진 알았으니 좀더 자세히 확인해보기 위해 디컴파일링 하여 소스코드를 확인해보겠습니다!
입력값 : www.naver.com | 네이버 페이지 |
3. 소스코드를 보니 loadUrl 을 통해 저희가 입력한 Url을 web view로 열어주고 있습니다만.. 이상하게도 web view 에 대한 권한설정이 찾아도 보이질 않습니다?... 이러면 web view를 통해 앱 내부저장소에 있는 파일들을 확인할 수 있습니다. 더군다나 지금 이 앱은 외부파일에 접근권한을 허용하고 있는상태라 앱 외부의 파일까지 접근할 수 있는 위험성이 있습니다!!...
취약한 loadUrl 코드 부분 |
AndroidManifest.xml 에서 선언된 외부파일에 접근권한 허용 부분 |
4. 그리하여 file:///data/data/jakhar.aseem.diva/shared_prefs/jakhar.aseem.diva_preferences.xml 라는 url 을 입력하여 앱 내부의 평문저장된 계정정보를 흭득한 것을 확인하실 수 있습니다.
※ 참고로 files///: 라고 하는 형식은 데스크톱(크롬) 에서 files///:C\ 라고 하게되면 로컬컴퓨터의 파일이 뜰것이고, Android 에서 하게되면 앱의 로컬파일들이 뜨게 됩니다. 여기서 files///: 랑 files//: 의 차이가 존재합니다.
files//: >> 상대경로로써 현재 자신이 위치한 폴더가 기준점이됨.
files///: >> 절대경로로써 기준이 잡힌게 아닌 경로 전체를 적어줘야함
그래서 위에 web view에 url 을 적을때 현재 위치한 디렉토리가 어디인지 기준을 모르기 때문에 절대경로로 넣어주었습니다!
[대응방안]
액티비티의 파일권한 설정을 해주면 됩니다. 현재 액티비티에서 WEB VIEW를 통한 파일접근 권한설정을 해주게 되면 url 을 통해서 내부 저장파일에 접근할 수 없게 됩니다! 또한 입력값 검증을통해 file:// 같이 디렉토리에 접근할 수 있는 문구들을 블랙리스트 필터링 해주어야 합니다. 그리고 애초에 Web view로 연결되는 url 을 입력하게 해선 안됩니다!..
아래는 접근 권한 설정을 해주어 다시 작성한 코드입니다.
[해당 내용을 반영하여 다시 작성한 코드]
package jakhar.aseem.diva;
/* import 에 대한 내용*/
public class InputValidation2URISchemeActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_input_validation2_urischeme);
WebView wview = (WebView) findViewById(R.id.ivi2wview);
WebSettings wset = wview.getSettings();
// JavaScript 활성화
wset.setJavaScriptEnabled(true);
// 보안을 강화하기 위해 파일 접근 및 콘텐츠 접근 비활성화
wset.setAllowFileAccess(false);
wset.setAllowContentAccess(false);
}
public void get(View view) {
EditText uriText = (EditText) findViewById(R.id.ivi2uri);
WebView wview = (WebView) findViewById(R.id.ivi2wview);
String url = uriText.getText().toString();
// file://로 시작하는 URL을 제외하고 다른 URL만 로드
if (!url.startsWith("file://")) {
wview.loadUrl(url);
}
}
}
3. Input Validation Issues - Part 3
1. 문제를 읽어보면 앱을 충돌나게 하는것이 목표인 문제인것 같습니다..아무 번호나 입력하니깐 역시나 접근 제한이 되었다는 문구가 뜹니다. 그러면 어떻게 이루어져 있는지 소스코드를 한번 보죠!
Input Validation Issues 페이지 | 접근 제한되는 모습 |
2. InputValidation3Activity 에 들어와 보면 이런 소스코드가 적혀있습니다. DivaJni 에 정의되어 있는 initiateLaunchSequence 라는 함수를 통해 입력값과 코드가 일치하면 미사일 버튼이 동작되는 방식인 것 같습니다. 그러면 DivaJni 가 어떻게 정의되어 있는지 한번 확인해보겠습니다.
3. DivaJni 클래스파일에 왔지만 함수가 어떻게 정의되어 있는지는 divajni 라는 라이브러리에 정의되어있는것 같습니다.. 라이브러리 파일은 바이너리 파일로써 Jadx 로는 디컴파일이 불가능하니 리버싱 툴인 Ghidra 를 이용하여 어셈블리어로 변환시켜주겠습니다. 참고로 divajni 라이브러리 파일은 리소스>lib에 가시면 있습니다!
InputValidation2의 액티비티 |
libdivajni.so 라는 라이브러리 파일 |
4. Ghira를 실행하여 저희가 확인해볼 라이브러리 파일을 리버싱 해주겠습니다.
5. 우선 왼쪽 바에 Symbol Tree 에다가 initiateLaunchSequence 라고 적어줘서 저희가 찾는 함수의 내용의 위치를 찾아줍니다. 이후 확인해 보니 CMPSB 라는 문자열 비교 연산자를 사용하고 있습니다. 아마 저 부분에서 저희가 입력한 값과 비밀코드를 비교하여 승인이 결정나는 부분인 것 같습니다. 그리고 옆에 주석을보니 비교되는 비밀코드는 " .dotdot " 이라는 문자열인것 같습니다.
6. 이로써 비밀코드는 찾았지만... 문제의도인 앱을 충돌나게 하라는 부분은 아직 못찾았습니다. 하지만 코드를 살펴보니 이 함수에서 strcpy 라는 함수를 사용하고 있습니다. 저희가 입력한 값은 pcVar1 이라는 변수에 들어가고 그 변수를 local_20[16] 이라는 사이즈 16 짜리 문자배열에 strcpy 를 사용하여 저장합니다.
여기서 중요한 점은 C언어를 공부해보신 분들은 아시겠지만 strcpy 는 문자가 입력되는 size를 지정할수가 없습니다. 즉! 저희가 16글자보다 더 많은 값을 입력해버리면 버퍼 오버플로우가 발생하여 앱의 문제가 생기도록 유도할 수가 있습니다!! 이런이유로 실제로 strcpy 보단 strcpy_s , strncpy 라는 버퍼오버플로우를 방지하는 함수를 사용합니다.
7. 그러면 문제로 돌아와서 16글자보다 훨씬 많은글자를 넣어주겠습니다. 저는 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 라는 글자를 넣어보았습니다. 그러자 문제 의도대로 버퍼오버플로우가 발생하여 Diva앱이 충돌난것을 확인하실 수 있습니다.
긴 문자열 입력 | Diva 앱이 충돌 |
8. 그러면 충돌도 일으켰으니 아까 저희가 찾아냈던 비밀코드를 집어넣어 이번에는 미사일 버튼을 발사해보겠습니다! .dotdot 이라는 문자열을 삽입하고 실행시키자 Launching T -10 ... 라는 문구가 뜨는것을 확인하실 수 있습니다!!
.dotdot 이라는 문구 삽입 | Launching T -10 .... 이라는 문구 확인 |
[대응방안]
> 버퍼오버플로우
: 입력값이 들어갈 메모리 외에 다른 메모리까지 침범하여 악용할 수 있으니 strcpy 라는 함수말고 strncpy 혹은 strcpy_s 를 사용하여 버퍼오버플로우 취약점을 막을 수 있다. 또한 입력을 할수있는 글자개수의 제한을 두어 막을수 있다.
> 하드코딩 이슈
: 이 문제에선 사실 비밀코드가 Ghidra 를 통해 노출되는 것 또한 문제였다. 이를 위해서 문자열을 비교할때도 저장되는 비밀코드에 Hash 암호화 과정을 추가하여 디컴파일링을 하더라도 어떤 문자인지 알아낼수 없게 만들어야 한다. 그리고 미사일 버튼인데 .dotdot 같은 문자열은 Hash 암호화를 한들 복호화 하기 상대적으로 쉬운 암호이다. 그러므로 좀더 복잡한 비밀번호를 사용하는 것이 권장된다.
접근 제어 이슈
※ Access Control Issues 란?
> 접근 제어 이슈라고도 하는 이 취약점은 제 기준으로는 웹 해킹으로 따지면 불충분한 인가 취약점에 가깝습니다. 예를들어 관리자 페이지로 이동하는 특정 동작이 있는데 이를 앱 외부에서 호출하여 관리자가 아니더라도 우회하여 접근할 수 있는경우 , PIN 번호를 입력해야 동작하는 것을 PIN번호 없이 동작시킬 수 있는 경우가 있습니다.
[실습]
1. Access Control Issues - Part 1
1. 우선 Access Control Issues - Part 1 문제로 접근해줍니다.
2. 문제 의도를 보니 앱을 실행시키지 않은상태에서 외부에서 이 API 키가 보이는 페이지를 동작시켜보라는 말이네요. 즉! 민감한 동작을 앱 외부에서 실행시키는 문제점에 관한 것 같습니다.
버튼 클릭 | API 에 대한 정보 |
3.일단 코드흐름을 살펴보기 위해 Jadx 로 디컴파일링 하여 소스코드를 확인해줍니다. AccessControl1Activity 의 코드를 살펴보았는데 Intent에 VIEW_CREDS 라는 액션을 실행시키는 것을 확인하실 수 있습니다. 이게 무슨 액션일까요??
4. 이번엔 AndroidManifest.xml 에 이 액션과 관련된 액티비티가 있는지 살펴보았습니다. 그러자 VIEW_CREDS 라는 액션이 APICredsActivity에서 실행되는것을 확인하였습니다(APICredsActivity는 API 정보를 출력해주는 액티비티 입니다). 문제는 intent-filter 를 사용하였으나 "exported="false" 설정을 빠뜨려서 이 action을 앱 외부에서도 동작시킬 수 있다는 문제점이 있습니다. (Intent Filter 사용시 아무런 설정을 안해주게 되면 기본적으로 exported="true" 로 설정이 되어 앱 외부에서도 실행이 가능합니다)
5. 그러면 cmd 창을 통해 앱 외부에서 해당 액션을 실행시켜보겠습니다! 다음과 같이 명령어를 작성합니다.
> nox_adb shell am start -a jakhar.aseem.diva.action.VIEW_CREDS
am > Acitivty Manager 의 약자로 액티비티를 실행합니다.
-a "action 이름" > 지정된 action 을 동작합니다.
>> VIEW_CREDS 의 Intent-Filter가 APICredsActivity 코드 내에 존재했으므로 이 액션을 동작시 APICredsActivity 켜질것입니다.
6. 그러자 성공적으로 APICredsActivity가 열린것을 확인하실 수 있습니다.
[대응방안]
> Intent-Filter 의 권한설정 미흡으로 발생한 문제입니다. 따라서 xml 파일에 다음과 같이 수정하여야 합니다.
<activity
android:label="@string/apic_label"
android:name="jakhar.aseem.diva.APICredsActivity"
android:exported="false"> <!-- 여기를 추가!! -->
<intent-filter>
<action android:name="jakhar.aseem.diva.action.VIEW_CREDS"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
2. Access Control Issues - Part 2
1. 일단 Access Control Issues - Part2 문제에 들어왔습니다. 문제 의도는 API 정보를 얻으려면 우선 Tveeter 이란곳에 가입을 한 후 , PIN 번호를 받아야 API 정보를 볼 수 있다고 합니다. 즉! 이 PIN번호가 없는상태로 외부에서 API 정보에 접근해라! 입니다. 일단 RadioButton 이 있으니 각각의 경우에 어떻게 나오는지 확인해보겠습니다.
2. Register Now 을 선택후 클릭하면 PIN 번호를 입력해야지 API에 접근가능한 페이지가 뜨고 , Already Registered 를 선택후 클릭하면 API 정보가 뜹니다. 아마 Already Registered 경우는 이런식으로 API 정보가 저장되어있다! 를 보여주고싶어서 넣어놓은것 같습니다. 저희는 첫번째 경우에 PIN 번호없이 어떻게 우회해서 API 정보에 접근할까를 고민해보면 될것같습니다.
Register Now 선택시 |
||
Already Reg 선택시 |
3. AccessControl2Activity 의 소스코드를 살펴보니 chk_pin이라는 boolean타입 변수에 따라 저희가 가입했는지 안했는지 유무가 저장되는 것 같습니다. 또한 VIEW_CREDS2 라는 액션을 동작시킨후 chk_pin 에 저장된 참/거짓을 putExtra를 사용해서 다음 이동될 액티비티에 전달하고 있습니다. 일단 VIEW_CREDS2 라는 액션이 어떤 액션인지 살펴봅시다.
AccessControl2Activity 소스코드 |
3. AndroidManifest.xml 에 접근하여 소스코드를 살펴보니 VIEW_CREDS를 동작하면 APICreds2Activity 라는 액티비티가 동작하는것 같습니다. 그러면 APICreds2Activity 는 어떻게 이루어져 있나 확인해보겠습니다.
AndroidManifest.xml 소스코드중 일부 |
4. 핀번호 틀릴때 뜨는 문구가 있는것을 보니 여기가 저희가 우회해야할 PIN번호 입력 액티비티 인것 같습니다. 보시면 아까 putExtra를 통해 전달해준 chk_pin 이라는 boolean 변수값을 Intent에서 가져와서 bcheck 에 넣습니다. 그리고 bcheck 가 true면 PIN번호가 틀렸다고 뜨고 , false 면 API정보를 반환하는것 같습니다. 즉 저희는 저 bheck를 false로 만들어야 하는겁니다!
참고로 getBooleanExtra( "Key 값", "해당 Key 값이 없다면 Default값") >> intent로 전달된 boolean값 가져오기
APICreds2Activity 의 소스코드중 일부 |
5. 넘어온 Intent 에서 key값이 getString(R.string.chk_pin) 인 boolean 타입의 변수를 가져와서 bcheck에 넣어주는것 같습니다. 만약 넘어온 값이 없다면 디폴트값은 true 라고 되어있습니다. 그러면 getString(R.string.chk_pin) 가 뭐라고 쓰여있는 문자열인지만 알면 그 값을 adb shell am 기능으로 조작할 수 있습니다. 참고로 adb shell am 기능은 액티비티를 실행할때 전달하는 변수값을 추가해줄수 있습니다.
APICreds2Activity 소스코드 중 일부 |
6. Jadx 에서 텍스트 검색을 키신후 chk_pin 이라고 검색해주세요. 여기서 검색창 밑에 정의 검색 부분을 전부 체크해주셔야 낱낱이 볼수 있습니다. res/values/strings.xml 에 chk_pin 에 대한 정의가 되어있는것 같으니 클릭합니다.
7. R.string.chk_pin 이라는 값은 사실 "check_pin" 이라는 문자열이였습니다.
※ 왜 굳이 이렇게 따로 정의해서 쓰냐면 나중에 프로젝트가 커졋을때 chk_pin 값을 바꿔야 하는 경우가 있다고 생각해봅시다. 그러면 액티비티 하나하나에 다 들어가서 일일이 다 바꿔주는것보단 저렇게 정의된 부분으로 가서 바꿔주면 훨씬 편하기 때문에 저런식으로 사용하게 됩니다.
8. 즉! 저희가 조작해줄 getBooleanExtra 소스코드는 아래 사진에 설명되었듯이 저런식으로 표현 되어있는것이나 마찬가지 입니다. 이제 key 값이 check_pin 인것도 알았으니 명령어 창으로 가서 bcheck 값을 우회해보겠습니다.
9. 명령어 창에서 다음과 같은 명령어를 작성해주세요.
> nox_adb shell am start -n jakhar.aseem.diva/.APICreds2Activity -a jakhar.aseem.diva.action.VIEW_CREDS2 --ez check_pin false
-n > jakhar.aseem.diva 라는 패키지에 있는 APICreds2Activity 라는 액티비티를 실행한다
-a > jakhar.aseem.diva.action.VIEW_CREDS2 라는 액션을 동작시킨다
--ez > 열릴 액티비티에 key 값이 check_pin 이고 value가 false인 변수를 전달해준다.
10. 명령어를 입력하자 API 정보가 나오도록 if 문을 우회한것을 확인할 수 있습니다.
[대응방안]
1. 우선 Part 1 문제와 똑같이 AndroidManifest.xml 파일에서 Intent-filter 부분 권한설정이 미흡합니다. 외부에서 View_Creds2 라는 액션을 동작시킬수 있으므로 android:exported="false" 옵션을 추가하여야 합니다.
2. 인증과정을 공격자가 조작할 수 있는 변수에 의존한다는 것이 문제입니다. API 정보같이 민감한 정보는 서버에서 저장 및 제공해야 하며 , 인증절차 또한 입력한 PIN번호를 Hash 암호화를 하여 서버에 저장된 PIN번호와 대조하여 인증절차를 거치는 방식으로 변경하여야 안전합니다.
3. Access Control Issues - Part 3
1. Part3 문제에 들어와보면 " 단 한번만 PIN번호를 만들어서 저만의 마이페이지를 만들수 있고 , 그 PIN번호로만 만든 마이페이지에 들어올수 있습니다. PIN번호를 만든후 앱 외부에서 PIN번호를 모른다 가정하고 마이페이지에 접근해보세요" 라고합니다.
2. 핀번호를 4251로 지정하고 일단 PIN번호를 만들어주었습니다. 이후 GO TO PRIVATE NOTES 로 들어가서 4251을 눌러주었더니 개인정보가 뜨는 방식이였습니다. 일단 얼추 동작은 이해했으니 이제 이 PIN번호를 모른다 가정하고 저 개인정보를 얻기위해 Jadx로 코드를 분석해보겠습니다.
3. 보시면 addPin 이라는 함수인것으로 보아 여기가 비밀번호 생성 부분인것 같습니다. 저장된 비밀번호를 SharedPreferences 에 저장하는 것으로 보이고 , 이후 버튼을 눌러서 AccessControl3NotesActivity 로 이동하는것을 확인하실 수 있습니다. 그러면 일단 AccessControl3NotesActivity는 어떻게 동작하는지 확인해보겠습니다.
AccessControl3Activity 소스코드중 일부 |
4. AccessControl3NotesActivity 의 소스코드를 보니 Sharedpreference에 저장한 값과 저희가 입력한 값을 equals 함수를 이용하여 비교하는 방식으로 인증절차를 거치고 있습니다. 여기서 눈여겨 봐야할 부분은 getContentResolver 라는 기능을 사용하고 있다는 것입니다. NotesProvider 라는 곳에서 Content URI 를 통해 컬럼이 id , title , note 인 내용을 가져오는것 같으니 소스코드에서 NotesProvider라는 클래스를 확인해봅시다.
AccessControl3NotesActivity 소스코드중 일부 |
[ContentResolver 가 뭔데?]
ContentProvider 과 상호작용할 수 있게 만드는 도구라고 생각하시면 됩니다.
그러면 ContentProvider란 뭐지? 라는 의문이 들것입니다.
ContentProvider란 기본적인 CRUD 기능을 가진 클래스입니다.
ContentProvider에 DB를 연결하면 앱 내부나 외부에서도 이 DB에 CRUD 기능을 사용할 수 있습니다.
이때 DB에 접근을 URI 를 통해 접근이 가능합니다.
아래 사진과 같은 방식으로 content:// 형식의 URI 를 통해 외부에서 DB에 접근이 가능합니다.
5. NotesProvider 의 코드를 보면 ContentProivder의 URI 가 "content://jakhar.aseem.diva.provider.notesprovider/notes" 라고 지정되어있습니다. DB의 테이블 명이 notes 인것으로 보아 저 URI 를 통해서 외부에서 notes 라는 테이블의 CRUD 기능을 사용할 수 있을것으로 보입니다.
NotesProvider 코드중 일부 |
6. 또한 AndroidManifest.xml 파일을 보시면 android:exported="true" 라는 설정이 되어있습니다. 이를통해 외부 앱에서 NoteProvider 클래스 내용에 접근이 가능해집니다. 내부 컴포넌트 뿐만 아니라 외부 앱에서도 NoteProvider 클래스에서 URI 를 사용하여 DB에 접근이 가능해지는것이죠!
7. 그러면 CMD 명령어창을 통해 content 명령어를 사용하여 ContentProvider 기능을 사용할 수 있습니다. ADB Shell의 content 명령어는 사실상 ContentProvider의 인터페이스를 활용하여 데이터를 읽고 쓸 수 있게 해주는 도구입니다. 주요기능으로는 query(조회) , update(수정) , delete(삭제) , create(생성) 이 있습니다. 따라서 외부 cmd창에서 NoteProvider의 기능을 사용하여 DB를 조회해볼것입니다. 다음의 명령어를 따라해주세요!
아래 사진을 보시면 contentProvider 를 통해 개인정보가 담겨있던 DB에 접근하여 내용을 조회한것을 확인하실 수 있습니다!
> nox_adb shell content query --uri content://jakhar.aseem.diva.provider.notesprovider/notes
[대응방안]
1. "exported = false" 설정
> 만약에 앱 내부데이터 끼리만 사용하는거라면 false 로 설정을 해두어 사고를 방지하자
2. 커스텀 권한 설정
> xml 파일에 추가적으로 요구하는 권한을 설정하여 , 이 권한을 가진 어플만 접근할 수 있도록 합니다. 특히 protectionLevel 옵션을 dangerous 로 설정하게 되면 사용자에게 퍼미션을 승인할 건지 요구하는 과정이 추가되기 때문에 보다 더 안전하게 ContentProvider 를 사용할 수 있습니다.
※코드 예시
<!-- AndroidMenifest.xml 파일에 추가 -->
<permission
android:name="jakhar.aseem.diva.permission.MY_PERMISSION"
android:protectionLevel="dangerous" />
<provider
android:name="jakhar.aseem.diva.NotesProvider"
android:enabled="true"
android:exported="true"
android:authorities="jakhar.aseem.diva.provider.notesprovider"
android:permission="jakhar.aseem.diva.permission.MY_PERMISSION" />
긴 글 읽어주셔서 감사합니다!
'모바일 앱해킹(Android) > Android DIVA' 카테고리의 다른 글
[Android DIVA] Access Control Issues 취약점 (0) | 2024.11.06 |
---|---|
[Android DIVA] Input Validation Issues 취약점 (0) | 2024.11.03 |
[Android DIVA] Insecure Data Storage 취약점 (0) | 2024.11.02 |
[Android DIVA] Hardcoding Issues 취약점 (0) | 2024.10.30 |
[Android DIVA] '취약한 로깅' 취약점 (0) | 2024.10.28 |