Introduction
하드코딩(Hardcoding)은 민감한 데이터(비밀번호, API 키, 비즈니스 로직 등)를 소스 코드에 직접 삽입하는 잘못된 개발 관행입니다. 개발자들은 종종 빠른 테스트를 위해 구현하지만, 나중에 이를 제거하는 것을 잊곤합니다.
안드로이드 앱에서 디컴파일 도구를 사용하면 APK 파일을 리버스 엔지니어링하여 이러한 민감 정보를 평문으로 찾아낼 수 있습니다. 이것은 간단해 보이지만 다음과 같은 문제를 가져올 수 있습니다.
- 비즈니스 로직 노출 및 우회
- 타사 API 키 탈취 및 할당량 도용
- 클라우드 인프라 자격 증명 유출
- 민감 정보 침해
HARDCODE ISSUES - Shopping Cart
취약점 개요
하드코딩은 단순히 비밀번호나 API 키의 문제만이 아닙니다. 비즈니스 규칙과 로직 자체가 클라이언트 측에 노출되는 것도 심각한 보안 취약점입니다.
많은 애플리케이션이 할인 쿠폰, 프로모션 코드, 결제 검증 로직을 클라이언트 측에서 처리합니다. 이는 공격자가 앱을 디컴파일하여 해당 코드를 찾아내고, 정당한 비용 지불 없이 서비스나 제품을 이용할 수 있게 만듭니다.
취약점의 위험성
이 취약점이 악용될 경우 다음과 같은 피해가 발생할 수 있습니다:
- 금전적 손실: 무료 또는 할인된 가격으로 제품/서비스 이용
- 매출 감소: 프로모션 코드의 무단 공유 및 확산
- 비즈니스 신뢰도 하락: 결제 시스템 우회 사실이 알려질 경우 브랜드 이미지 손상
- 부정 거래 증가: 실제 판매 없이 주문만 증가하여 물류 및 재고 관리 혼란
기능 분석
해당 기능은 할인을 받기 위한 프로모션 코드를 요구하고 있습니다.
일반적인 사용자는 유효한 코드를 입력할 때 할인이 적용되지만, 그렇지 않으면 정상 가격으로 결제하게 됩니다. 문제는 이 검증 로직이 서버가 아닌 클라이언트(앱) 내부에서 이루어진다는 점입니다.
즉, 앱 코드 안에 프로모션 코드가 평문으로 저장되어 있을 가능성이 높습니다.
취약점 원인 분석

PS C:\Users\WIN11> adb shell dumpsys window | findstr "mCurrentFocus"
mCurrentFocus=Window{5389360 u0 owasp.sat.agoat/owasp.sat.agoat.HardCodeActivity}
쇼핑 카트 기능에서 액티비티명을 확인합니다. 그런 다음, JADX를 통해 해당 액티비티의 코드를 확인합니다.

package owasp.sat.agoat;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import kotlin.text.StringsKt;
/* compiled from: HardCodeActivity.kt */
@Metadata(m131d1 = {"\u0000\u001e\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0005\u001a\u00020\u00062\b\u0010\u0007\u001a\u0004\u0018\u00010\bH\u0014R\u000e\u0010\u0003\u001a\u00020\u0004X\u0082D¢\u0006\u0002\n\u0000¨\u0006\t"}, m132d2 = {"Lowasp/sat/agoat/HardCodeActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "promoCode", "", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "app_debug"}, m133k = 1, m134mv = {1, 8, 0}, m136xi = ConstraintLayout.LayoutParams.Table.LAYOUT_CONSTRAINT_VERTICAL_CHAINSTYLE)
/* loaded from: classes2.dex */
public final class HardCodeActivity extends AppCompatActivity {
private final String promoCode = "NEW2019";
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(C1278R.layout.activity_hard_code);
Button verifyButton = (Button) findViewById(C1278R.id.hardcode1);
final TextView priceValue = (TextView) findViewById(C1278R.id.price);
final EditText promoCodeValue = (EditText) findViewById(C1278R.id.promocode);
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Alert!");
verifyButton.setOnClickListener(new View.OnClickListener() { // from class: owasp.sat.agoat.HardCodeActivity$$ExternalSyntheticLambda0
@Override // android.view.View.OnClickListener
public final void onClick(View view) {
HardCodeActivity.onCreate$lambda$1(promoCodeValue, this, priceValue, builder, view);
}
});
}
/* JADX INFO: Access modifiers changed from: private */
public static final void onCreate$lambda$1(EditText $promoCodeValue, HardCodeActivity this$0, TextView $priceValue, AlertDialog.Builder builder, View it) {
Intrinsics.checkNotNullParameter(this$0, "this$0");
Intrinsics.checkNotNullParameter(builder, "$builder");
if (StringsKt.equals($promoCodeValue.getText().toString(), this$0.promoCode, true)) {
$priceValue.setText("0");
builder.setMessage("Congratulations! You got this product for free");
Toast.makeText(this$0, "Congratulations! You got this product for free", 1).show();
} else {
builder.setMessage("Sorry! Incorrect Promocode was entered");
Toast.makeText(this$0, "Sorry! Incorrect Promocode was entered", 1).show();
$priceValue.setText("2000");
}
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { // from class: owasp.sat.agoat.HardCodeActivity$$ExternalSyntheticLambda1
@Override // android.content.DialogInterface.OnClickListener
public final void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
AlertDialog dialog = builder.create();
Intrinsics.checkNotNullExpressionValue(dialog, "builder.create()");
dialog.show();
}
}
HardCodeActivity 분석 결과 promoCode라는 문자열에 프로모션 코드가 하드코딩되어 있는 것을 확인했습니다.
입력칸에 사용자가 입력한 값과 하드코딩된 값이 같다면 Congratulations 문자열이 나올 것입니다.
취약점 검증 (Proof of Concept)
테스트 #1: 하드코딩된 프로모션 코드 활용
AndroGoat 앱 내에서 Shopping Cart 메뉴로 이동합니다.

사용자 입력 칸에 이전에 확인했던 문자열인 NEW2019를 입력한 뒤 VERIFY 버튼을 클릭하면 성공적으로 프로모션 코드가 적용되는 것을 볼 수 있습니다.
HARDCODE ISSUES - AI CHAT
취약점 개요
현대의 많은 모바일 애플리케이션은 OpenAI(ChatGPT), Google Cloud AI, AWS Comprehend와 같은 타사 AI 서비스를 활용합니다. 이러한 서비스와 통신하기 위해서는 API 키가 필요한데, 이 키가 앱 내부에 하드코딩되어 있는 경우가 빈번합니다.
API 키는 일반적으로 유료 서비스이므로, 공격자가 이를 탈취하면 개발자의 계정을 사용하여 무제한으로 API를 호출할 수 있습니다. 이는 막대한 금전적 피해로 이어질 수 있습니다.
취약점의 위험성
타사 API 키가 유출될 경우 발생할 수 있는 피해:
- 할당량(Quota) 도용: 공격자가 개발자의 유료 할당량을 무단으로 소진
- 막대한 청구서 발생: OpenAI, AWS 등에서 예상치 못한 수십만 원 ~ 수천만 원의 요금 청구
- 서비스 중단: 할당량 초과로 인한 정상 사용자의 서비스 이용 불가
- 데이터 유출: API 키를 통해 저장된 데이터나 로그에 접근 가능한 경우도 존재
기능 분석

AI Chats 메뉴로 이동 후 확인하면 간단히 대화할 수 있는 기능인 것 같습니다.
이러한 API 호출은 백엔드 서버를 통해 프록시 되어야 하지만, 간편함을 위해 클라이언트에서 직접 API를 호출하도록 구현되어있을 수도 있습니다. 만약 이렇게 구현된 앱의 경우 API 키가 앱 코드 내 하드코딩되어 있을 가능성이 높습니다.
취약점 원인 분석

PS C:\Users\WIN11> adb shell dumpsys window | findstr "mCurrentFocus"
mCurrentFocus=Window{6447126 u0 owasp.sat.agoat/owasp.sat.agoat.AIChatActivity}
현재 페이지의 액티비티 명인 AIChatActivity를 확인했습니다. 이후 디컴파일한 코드를 분석해보겠습니다.\

OpenAI API 키는 "sk-"로 시작하는 고유한 형식을 가지고 있어 쉽게 식별할 수 있으며, 이 키를 추출하면 즉시 사용 가능합니다.
HARDCODE ISSUES - View Cloud Services
취약점 개요
개발자들은 AWS, Azure와 같은 클라우드 플랫폼의 액세스 키와 시크릿 키를 앱에 직접 삽입하기도 합니다.
클라우드 자격 증명이 유출되면 공격자는 회사의 전체 인프라에 접근이 가능합니다. 데이터베이스, 스토리지 등 완전히 장악이 가능하게 됩니다.
취약점의 위험성
클라우드 자격 증명 유출 시 발생 가능한 치명적 피해:
- 전체 인프라 장악: 공격자가 AWS 콘솔에 접근하여 모든 리소스 제어
- 데이터 유출: S3 버킷의 고객 데이터, 백업 파일, 민감 정보 다운로드
- 데이터 삭제 및 파괴: 중요한 데이터베이스 삭제, 백업 파일 손상
- 암호화폐 채굴: EC2 인스턴스를 생성하여 비트코인 채굴, 막대한 요금 청구
- 랜섬웨어 공격: 데이터를 암호화하고 금전 요구
기능 분석
Cloude Services 메뉴로 이동합니다.

앱은 클라우드 스토리지에 파일을 업로드하거나 다운로드하기 위해 AWS SDK를 사용합니다. 이 과정에서 AWS Access Key ID와 Secret Access Key가 필요하며, 이들이 앱 코드에 하드코딩되어 있을 수 있습니다.
취약점 원인 분석

먼저 현재의 액티비티명인 CloudeServicesActivity를 확인합니다. 이후 Jadx를 통해 해당 액티비티의 디컴파일 코드를 확인해보겠습니다.

디컴파일한 코드 내 AWS Access Key와 Secert Key를 획득할 수 있습니다. 이를 통해 AWS 자격 증명으로 로그인이 가능하며 만약 권한이 부여되어있는 계정이라면 전체 서버 장악까지 가능하게됩니다.
Comments
Sign in with GitHub to leave a comment.