이번 포스트에선 디바 2번과 12번 문제인 하드코딩된 이슈 문제들을 풀어볼까 한다.

 

문제를 풀기 전 하드코딩된 중요정보가 어디서 자주 나오는지에 대해 알아보자.

하드코딩된 중요정보 예시로 ID와 PW가 있다. PHP나 JAVA로 DB에서 어떠한 데이터를 가져온다고 가정해보자.

 

DB로부터 데이터를 가져오려면 ID와 PW가 필요하다.

 

보통 이를 Environment, Config 파일 또는 코드단 내에 저장한다.(복잡하게 하려고 키 관리 시스템 쓰는 경우도 많음.)

 

이런 파일들은 주로 서버단에 위치해 있다. 클라이언트 측에서 이를 보려면 파일 다운로드 취약점, 서버 탈취, SSRF 등 취약점을 이용해야 한다.

 

안드로이드 앱의 경우는 어떤가? 누구나 소스코드를 볼 수 있기 때문에 소스코드 단에 하드코딩된 중요정보가 있으면 쉽게 탈취당할 수 있다. 이는 서버에서 클라이언트로 보내주는 프론트 코드라고 생각하면 된다. 프론트 단에 중요정보가 있으면 당연히 안된다.

 

하지만 실제로 보면 배포 전 제대로된 검사가 이루어지지 않아서 웹 소스코드(프론트)에 AWS 시크릿 키, 테스트 계정 정보, S3 버킷 주소 등 여러 중요정보들이 하드코딩된 경우가 많다. 안드로이드 앱도 마찬가지다.

 

이를 어찌 확인할 수 있을까?

1. 툴 돌린다

2. 코드 하나씩 까본다(쌩 노가다)

3. 인증 기능이 있을 때 마다 그와 관련된 코드들 까본다

 

이번 경우는 코드양이 그리 많지 않아서 코드를 까보면 쉽게 인증 정보를 얻을 수 있다.

 

# 코드 분석 - Hardcoding issue Part 1

HardcodeActivity 파일 내 vendorsecretkey와 입력한 값이 맞을 시 접근 허락되는 로직이다.

    public void access(View view) {
        EditText hckey = (EditText) findViewById(R.id.hcKey);
        if (hckey.getText().toString().equals("vendorsecretkey")) {
            Toast.makeText(this, "Access granted! See you on the other side :)", 0).show();
        } else {
            Toast.makeText(this, "Access denied! See you in hell :D", 0).show();
        }
    }

 

# 취약점 테스트 - Hardcoding issue Part 1

패스

 

# 시큐어 코딩

인증을 클라이언트 단에서 하는 것이 문제이다. 텍스트를 받은 후 서버로 POST 요청을 보내 인증하도록 한다.

public void access(View view) {
    EditText hckey = findViewById(R.id.hcKey);
    String key = hckey.getText().toString();

    // 서버로 전송, OkHttp 라이브러리 함수
    sendToServer(key);
}

 

서버로 전달하는 부분만 명시한 이유가 있다.

 

원본 코드는 Access Grant랑 Deny만 명시되어 있는데, 실제 서비스라면 엑세스 허가 시 어떤 액티비티로 넘어가는 시스템일 것이다. 그런데 이는 잘못된 구현방식이다.

 

액티비티를 강제적으로 실행하면 그만이기도 하고, smali 코드에서 인증부분 조건문을 if-eq를 if-ne로 수정 후 APK로 리빌드한 뒤 사용해도 되기 때문이다. 또 다른 방식으로 서버에서 1을 전달 시 인증 통과하는 방식이라면 프록시 단에서 응답값을 수정하면 된다.

 

프리다를 이용해 후킹할 수도 있다!

 

되게 많은 우회 방법들이 있기에 만약 내가 개발자라면 인증 통과 시 그에 맞는 인증 토큰을 부여해주고 중요정보 접근 시 마다 이 토큰을 검사하는 방법이 제일 안전한 방법이 아닌가 싶다.

 

# 코드 분석 - Hardcoding issue Part 2

파트 1만 하기엔 양이 너무 적어서 2까지 한번에 할게용

 

Hardcode2Activity 파일을 보면 사용자 입력값을 hckey에 넣고 이 값을 this.djni.access 라는 알 수 없는 함수에 넣은 뒤 리턴값이 0이 아니면 통과하는 로직이 있다. 잘보면 private DivaJni djni 로 DivaJni를 dini에 할당한다. DivaJni 파일을 분석해보자.

public class Hardcode2Activity extends AppCompatActivity {
    private DivaJni djni;

    @Override // android.support.v7.app.AppCompatActivity, android.support.v4.app.FragmentActivity, android.support.v4.app.BaseFragmentActivityDonut, android.app.Activity
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hardcode2);
        this.djni = new DivaJni();
    }

    public void access(View view) {
        EditText hckey = (EditText) findViewById(R.id.hc2Key);
        if (this.djni.access(hckey.getText().toString()) != 0) {
            Toast.makeText(this, "Access granted! See you on the other side :)", 0).show();
        } else {
            Toast.makeText(this, "Access denied! See you in hell :D", 0).show();
        }
    }
}

 

DivaJni.java 파일은 divajni 라는 이름의 라이브러리를 로딩하는 함수이다. 이 라이브러리는 네이티브 라이브러리라고 부른다. 네이티브 라이브러리는 c나 c++로 만들어진 컴파일된 파일이다.

확장자는 so이며, 하드웨어단(카메라, 배터리) 조작 시 자바보단 c나 c++이 더 낫기 때문에 이 언어로 작성하고 이들을 네이티브 라이브러리라고 따로 부르는 것이다.

package jakhar.aseem.diva;

/* loaded from: classes.dex */
public class DivaJni {
    private static final String soName = "divajni";

    public native int access(String str);

    public native int initiateLaunchSequence(String str);

    static {
        System.loadLibrary(soName);
    }
}

 

 

lib 폴더에 libdivajni 이라는 파일이 있다. 이게 DivaJni.java에 의해 호출되는 네이티브 라이브러리다. 이를 그대로 읽을 순 없다.

 

# 취약점 테스트 - Hardcoding issue Part 2

다행히 우리에겐 디컴파일러 사이트와 툴이 있다. 파일을 업로드 한 뒤 디컴파일 해보자.

https://dogbolt.org/

 

Decompiler Explorer

Decompiler Explorer is an interactive online decompiler which shows equivalent C-like output of decompiled programs from many popular decompilers.

dogbolt.org

 

 

디컴파일 한 내용에서 pcVar3 에는 ols.. 문자가 정의되어 있고  pcVar3 == pcVar1가 일치하면 부울 논리에 의해 1이 uVar4에 할당된다. 그리고 이를 리턴한다. 여기서 pcVar1은 우리가 전달하는 값이다.

즉 ols.. 값 전달 시 부울 논리에 의해 1이 리턴되고, 우리가 봤었던 코드를 보면 != 0 시 Grant 되는 논리였으니 ols.. 가 정답이 되는 것이다.

생략
bool Java_jakhar_aseem_diva_DivaJni_access(long *param_1,undefined8 param_2,undefined8 param_3)

{
  char *pcVar1;
  long lVar2;
  char *pcVar3;
  undefined1 uVar4;
  byte bVar5;
  
  bVar5 = 0;
  uVar4 = 1;
  pcVar1 = (char *)(**(code **)(*param_1 + 0x548))(param_1,param_3,0);
  lVar2 = 0xb;
  pcVar3 = "olsdfgad;lh";
  do {
    if (lVar2 == 0) {
      return (bool)uVar4;
    }
    lVar2 = lVar2 + -1;
    uVar4 = *pcVar3 == *pcVar1;
    pcVar3 = pcVar3 + (ulong)bVar5 * -2 + 1;
    pcVar1 = pcVar1 + (ulong)bVar5 * -2 + 1;
  } while ((bool)uVar4);
  return (bool)uVar4;
}
생략

# 시큐어 코딩

Part 1에서 한 것과 마찬가지로 클라이언트 단에서 인증 과정을 거치면 안된다.

 

# 후기

심플하다면 심플한 문제풀이~! (실제론 훨씬 더 어려움... 코드 양도 많음.. 그거 다 이해해야함.. 난독화도 되어 있음.. 그거 다 풀어야됨...........)

+ Recent posts