目录
导语
篇幅有些长,请使用目录品用。
DIVA
DIVA (Damn insecure and vulnerable App),是一个故意设计为全身漏洞的APP软件,它能让开发人员、QA、安全人员了解到APP软件一般存在的问题。
github地址:https://github.com/payatu/diva-android
不安全日志输出
开发人员有意或无意地记录敏感信息(如凭据,会话ID,财务详细信息等)
Insecure Logging
我们输入的数字”123456789”,使用adb logcat 命令可以看到
E/diva-log( 3575): Error while processing transaction with credit card: 123456789
使用jd-gui看下logActivity.class 可以发现
Log.e("diva-log", "Error while processing transaction with credit card: " + paramView.getText().toString());
Toast.makeText(this, "An error occured. Please try again later", 0).show();
代码中使用了log.e,如果开发人员在debug时不小心忘记去掉log输出,将会造成很大的安全隐患。
不安全的数据存储
不安全的数据存储也是App常见的安全问题之一,主要有三种方式:
- 将敏感数据保存到配置文件中。
- 将敏感数据保存在本地的sqlite3数据库中。
- 将敏感数据保存在临时文件或者sd卡中。
Insecure Data Storage -Part1
使用了SharedPreferences类,该类是Android平台上一个轻量级的存储类,主要是用来保存一些常用的配置,本例中是用该类存储了用户名和密码,因此是具有风险的。SharedPreferences类存储的数据会以.xml的形式存储在/data/data/apppackagename/shared_prefs目录下
输入用户名和密码都为admin
使用adb连接手机查看文件
InsecureDataStorage1Activity.class
paramView = PreferenceManager.getDefaultSharedPreferences(this).edit();
EditText localEditText1 = (EditText)findViewById(2131493000);
EditText localEditText2 = (EditText)findViewById(2131493001);
paramView.putString("user", localEditText1.getText().toString());
paramView.putString("password", localEditText2.getText().toString());
paramView.commit();
Toast.makeText(this, "3rd party credentials saved successfully!", 0).show();
Insecure Data Storage -Part2
一般app对应的数据库目录: /data/data/apppackagename/databases
输入用户名,密码都为ceshi
使用adb查看数据库
InsecureDataStorage2Activity.class
SQLiteDatabase localSQLiteDatabase = this.mDB;
paramView = new java/lang/StringBuilder;
paramView.<init>();
localSQLiteDatabase.execSQL("INSERT INTO myuser VALUES ('" + localEditText1.getText().toString() + "', '" + localEditText2.getText().toString() + "');");
this.mDB.close();
Toast.makeText(this, "3rd party credentials saved successfully!", 0).show();
Insecure Data Storage -Part3
输入用户名和密码为demo
使用adb查看临时文件,目录在/data/data/apppackagename/
InsecureDataStorage3Activity.class
Object localObject2 = File.createTempFile("uinfo", "tmp", (File)localObject1);
((File)localObject2).setReadable(true);
((File)localObject2).setWritable(true);
localObject1 = new java/io/FileWriter;
((FileWriter)localObject1).<init>((File)localObject2);
localObject2 = new java/lang/StringBuilder;
((StringBuilder)localObject2).<init>();
((FileWriter)localObject1).write(localEditText.getText().toString() + ":" + paramView.getText().toString() + "\n");
((FileWriter)localObject1).close();
Toast.makeText(this, "3rd party credentials saved successfully!", 0).show();
Insecure Data Storage -Part4
存储sd卡的目录一般在: /mnt/sdcard
InsecureDataStorage4Activity.class
paramView = (EditText)findViewById(2131493010);
EditText localEditText = (EditText)findViewById(2131493011);
File localFile = Environment.getExternalStorageDirectory();
try
{
Object localObject1 = new java/io/File;
Object localObject2 = new java/lang/StringBuilder;
((StringBuilder)localObject2).<init>();
((File)localObject1).<init>(localFile.getAbsolutePath() + "/.uinfo.txt");
((File)localObject1).setReadable(true);
((File)localObject1).setWritable(true);
localObject2 = new java/io/FileWriter;
((FileWriter)localObject2).<init>((File)localObject1);
localObject1 = new java/lang/StringBuilder;
((StringBuilder)localObject1).<init>();
((FileWriter)localObject2).write(paramView.getText().toString() + ":" + localEditText.getText().toString() + "\n");
((FileWriter)localObject2).close();
Toast.makeText(this, "3rd party credentials saved successfully!", 0).show();
return;
}
可以看到使用Environment.getExternalStorageDirectory()来获取sd卡的目录,文件名为.unifo.txt
硬编码
- 某些需要比较字符串的值为硬编码,如:激活码
- 加密的key或者salt为硬编码,如MD5的salt
Hardcoding Issues -Part1
这里直接查看源码就知道答案
HardcodeActivity.class
if (((EditText)findViewById(2131492987)).getText().toString().equals("vendorsecretkey")) {
Toast.makeText(this, "Access granted! See you on the other side :)", 0).show();
}
for (;;)
{
return;
Toast.makeText(this, "Access denied! See you in hell :D", 0).show();
}
Hardcoding Issues -Part2
源码Hardcode2Activity.class
这里使用了DivanJni类,查看 DivaJni.class
这里加载了divajini库,一般库文件都放在/lib下,在目录下看到了很多架构,随便查看一个
arm64-v8a:
libdivajni.so
armeabi:
libdivajni.so
armeabi-v7a:
libdivajni.so
mips:
libdivajni.so
mips64:
libdivajni.so
x86:
libdivajni.so
x86_64:
libdivajni.so
linux下可以使用strings方便的查看二进制文件里的字符串
$ strings libdivajni.so
/system/bin/linker
__cxa_finalize
__cxa_atexit
Java_jakhar_aseem_diva_DivaJni_access
strncmp
__aeabi_unwind_cpp_pr1
Java_jakhar_aseem_diva_DivaJni_initiateLaunchSequence
strcpy
__aeabi_unwind_cpp_pr0
JNI_OnLoad
__aeabi_unwind_cpp_pr2
__gnu_Unwind_Find_exidx
__gnu_Unwind_Restore_VFP_D
__gnu_Unwind_Restore_VFP
__gnu_Unwind_Restore_VFP_D_16_to_31
__gnu_Unwind_Restore_WMMXD
__gnu_Unwind_Restore_WMMXC
abort
restore_core_regs
memcpy
_Unwind_GetCFA
__gnu_Unwind_RaiseException
__gnu_Unwind_ForcedUnwind
__gnu_Unwind_Resume
__gnu_Unwind_Resume_or_Rethrow
_Unwind_Complete
_Unwind_DeleteException
_Unwind_VRS_Get
_Unwind_VRS_Set
__gnu_Unwind_Backtrace
__cxa_begin_cleanup
__cxa_type_match
__gnu_unwind_execute
__cxa_call_unexpected
_Unwind_VRS_Pop
__gnu_Unwind_Save_VFP_D
__gnu_Unwind_Save_VFP
__gnu_Unwind_Save_VFP_D_16_to_31
__gnu_Unwind_Save_WMMXD
__gnu_Unwind_Save_WMMXC
__restore_core_regs
___Unwind_RaiseException
_Unwind_RaiseException
___Unwind_Resume
_Unwind_Resume
___Unwind_Resume_or_Rethrow
_Unwind_Resume_or_Rethrow
___Unwind_ForcedUnwind
_Unwind_ForcedUnwind
___Unwind_Backtrace
_Unwind_Backtrace
__gnu_unwind_frame
_Unwind_GetRegionStart
_Unwind_GetLanguageSpecificData
_Unwind_GetDataRelBase
_Unwind_GetTextRelBase
_edata
__bss_start
_end
libstdc++.so
libm.so
libc.so
libdl.so
libdivajni.so
olsdfgad;lh
.dotdot
GCC: (GNU) 4.8
gold 1.11
aeabi
.shstrtab
.interp
.dynsym
.dynstr
.hash
.rel.dyn
.rel.plt
.text
.ARM.extab
.ARM.exidx
.rodata
.fini_array
.init_array
.dynamic
.got
.data
.bss
.comment
.note.gnu.gold-version
.ARM.attributes
看里面又没特别的字符串,就像是olsdfgad;lh,试一下成功了
在下面链接看源代码就可以知道,vendorkey是硬编码。
https://github.com/payatu/diva-android/blob/master/app/src/main/jni/divajni.c
访问控制
Access Control lssues -Part 1
我们可以按下面的按钮获取到API 凭据:
我们的目标是不使用按钮就获取到API凭据,查看 AndroidManifest.XML文件,查看和供应商API凭据提供相关的activity:
<activity android:label=”@string/apic_label” android:name=”jakhar.aseem.diva.APICredsActivity”>
<intent-filter>
<action android:name=”jakhar.aseem.diva.action.VIEW_CREDS”/>
<category android:name=”android.intent.category.DEFAULT”/>
</intent-filter>
</activity>
通过观察代码发现,这个activity被intent filter所“保护”,当intent filter被activity等组件使用,这个activity可能会被外部任何应用程序所调用。
我们可以使用下面的命令:
$ adb shell am start jakhar.aseem.diva/.APICredsActivity
- adb shell: 进入手机shell模式
- am: activity 管理工具
- start: 启动activity
接着手机就弹出了之前的页面
上面的命令还有另一种写法: $ adb shell am start -n jakhar.aseem.diva/.APICredsActivity -a jakhar.aseem.diva.action.VIEW_CREDS
- -a:指定action
- -n:指定完整 component 名
Access Control lssues -Part 2
如objective所知,如果你已经注册,你就能拥有tveeter API Credentials,当前的挑战是,不注册来获取API Credentials。
和之前一样查看 AndroidManifest.XML文件:
<activity android:label=”@string/apic2_label” android:name=”jakhar.aseem.diva.APICreds2Activity”>
<intent-filter>
<action android:name=”jakhar.aseem.diva.action.VIEW_CREDS2″/>
<category android:name=”android.intent.category.DEFAULT”/>
</intent-filter>
</activity>
试下使用老套路:
$ adb shell am start -n jakhar.aseem.diva/.APICreds2Activity -a jakhar.aseem.diva.action.VIEW_CREDS2
可以看到我们跳入了选择未注册时相同的activity,这表明程序做了相应的措施,我们看下源代码
APICreds2Activity.class
从Intent中获取到上个activity传来的boolean值(2131099686),当这个值为false时就视为用户已注册,再看看上个activity。
AccessControl2Activity.class
这个类中bool值是通过单选项来决定的,而且把值传给下个activity。这时我们可以使用–ez来传递一个boolean键值对,还要获取到2131099686值对应的key值是什么,一般情况下使用apktool反编译的字符串文件存放在res/vaules/strings.xml文件下,string文件中所有的字符串资源都会被R.java的String类中被标识,每个字符串中都有唯一的iint类型索引值,在反编译的情况下所有的索引值都保存在strings.xml同目录下的public.xml文件中,所以,我们现在public中查看16进制的2131099686(0x7f060026 )对应的name值
<public type="string" name="chk_pin" id="0x7f060026" />
然后再strings文件下查找chk_pin,最后得到key 为check_pin。
<string name="chk_pin">check_pin</string>
最后的命令:
$ adb shell am start -a jakhar.aseem.diva.action.VIEW_CREDS2 -n jakhar.aseem.diva/.APICreds2Activity --ez check_pin false
Access Control lssues -Part 3
这是个私人的笔记app,一开始需要设置密码才能使用,我们的目标是不设置密码就开始使用。
查看 AndroidManifest.XML文件:
<provider android:authorities=”jakhar.aseem.diva.provider.notesprovider” android:enabled=”true” android:exported=”true” android:name=”jakhar.aseem.diva.NotesProvider”/>
这里使用了ContentProvider,android:enabled表示是否能由系统初始化,android:exported表示是否能被其他应用使用,android:authorities:标识这个ContentProvider,调用者可以根据这个标识来找到它,具体内容可以查阅其他资料。
看到2个值都为true,我们就可以使用content://访问里面的数据了,搜索包含content://的字符串文件:
smali/jakhar/aseem/diva/NotesProvider.smali
找到后和我们在AndroidManifest所看到的一样,我们可以使用以下命令随意的访问该uri:
$ adb shell content query –-uri content://jakhar.aseem.diva.provider.notesprovider/notes
就如我们设置密码进入后所看到的信息一样。
用户数据问题
从外部输入的组件获取到用户输入的数据后,在使用前应该进行检验长度和过滤等操作。
Input Validation Issues - Part1
这里面总共有3个用户的数据
当输入‘时,没有任何返回,查看logcat会发现:
出现了sql注入,我们输入1’ or ‘1’=’1时
所有用户数据都出来了,查看下对应的SQLInjectionActivity.class 源代码:
我们会发现开发人员并没有对用户的输入数据进行处理。
Input Validation Issues - Part2
直接输入http://www.baidu.com 可以看到打开了百度的页面
如果将http协议换成File协议(File协议主要用于访问本地计算机中的文件,就如同在Windows资源管理器中打开文件一样),那我们就可以读取敏感信息了。查看下之前存储在sd卡的账号文件,file:///mnt/sdcard/.uinfo.tx
这样我们就能读取手机内其他的文件信息了,查看下对应的 InputValidation2URISchemeActivity.class源码:
可以发现同样是没有对用户数据进行处理。
Input Validation Issues - Part3
简单输入AAAA,出现了正常的反应。
试下输入超级长的AAAA…就会发现activity会直接崩溃,查看logcat就能发现缓冲区溢出了。
查看源码
https://github.com/payatu/diva-android/blob/master/app/src/main/jni/divajni.c
使用的是strcpy,典型的字符串复制溢出。
参考来源:
http://bobao.360.cn/learning/detail/3048.html
http://resources.infosecinstitute.com/cracking-damn-insecure-and-vulnerable-apps-diva-part-1/