コース目次 / 第2章
ハードコードされた秘密を見つける
smaliにAPIキーを直書きしてビルドし、jadxの逆コンパイルで平文のまま読める様子を確認します。「コンパイルは隠すことではない」ことを体で理解します。
前章の MainActivity.smali に、1行足します。「秘密のAPIキーを、コードに直書きする」という、モバイルアプリで実際によく見られる実装です。
秘密を、埋め込む
.class public Lcom/example/vulnnotes/MainActivity;
.super Landroid/app/Activity;
# 【穴: ハードコードされた秘密】APIキーがバイトコードに直接埋め込まれている。
.field public static final API_KEY:Ljava/lang/String; = "sk_live_51H8xJ9AbCdEfGhIjKlMnOpQr"
.method public constructor <init>()V
.locals 0
invoke-direct {p0}, Landroid/app/Activity;-><init>()V
return-void
.end method
再ビルドします。
apktool b vulnnotes -o vulnnotes.apk
jarsigner -keystore debug.keystore -storepass android vulnnotes.apk debugkey
jadx で、読み出す
jadx -d out -f vulnnotes.apk
cat out/sources/com/example/vulnnotes/MainActivity.java
package com.example.vulnnotes;
import android.app.Activity;
public class MainActivity extends Activity {
public static final String API_KEY = "sk_live_51H8xJ9AbCdEfGhIjKlMnOpQr";
}
APIキーが、一字一句そのまま読めてしまいました。あなたは smali で書きましたが、実際のアプリの多くは Kotlin/Java で書かれ、コンパイルされて DEX になります。jadx のようなツールは、そのコンパイル済みバイトコードを、驚くほど元のソースに近い形に戻せます。
「コンパイル」は、暗号化ではない
sec-jwt で「JWTは暗号化ではない、署名しているだけ」と学びました。ここにも似た誤解があります——「コンパイルしたから、中身は見えないはず」は、間違いです。コンパイルは、人間が読みやすい形を、機械が実行しやすい形に変換しているだけで、情報を隠す仕組みではありません。jadx のような逆コンパイラは、その変換をほぼ元に戻せます。
難読化(obfuscation)という、変数名を意味のない文字列に変えるなどして読みにくくする技術もありますが、それも「読みにくくする」だけで、「読めなくする」わけではありません——最終的には、動かなければならないコードだからです。
どこに、秘密が紛れ込みやすいか
実際のアプリでよく見つかる、ハードコードされた秘密の置き場所です。
・ソースコードの定数(今回の例のように)
・strings.xml などのリソースファイル
・設定ファイル(.properties、.json をアセットとして同梱)
・ビルドの過程でしか使わないはずが、誤って本体に含まれてしまった鍵ファイル
診断では、文字列っぽいパターン(sk_、AIza のようなよくある接頭辞、長いランダム文字列)を、逆コンパイルしたソースやリソースから機械的に検索するのが定石です。
持ち帰る一言
秘密は、バイナリに焼き込まない。 コンパイルは変換であって暗号化ではなく、逆コンパイラでほぼ元に戻ります。事実(この場合は秘密の鍵)をクライアント側に持たせず、サーバ側に置く——sec-securecoding の原則が、ここでも同じ形で効きます。次は、コード以外の場所——マニフェストの誤設定を見ます。
こうなっていればOK
卒業まであと3章です。
この章はまだ完了していません。
保存できませんでした(プライベートブラウズ中かもしれません)。この端末に進捗は残りませんが、先へは進めます。